dappbooster 3.2.0 → 3.3.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/dist/app.js CHANGED
@@ -35,7 +35,7 @@ const App = ({ preselectedStack }) => {
35
35
  setCurrentStep(1);
36
36
  setAttempt((prev) => prev + 1);
37
37
  }, [preselectedStack]);
38
- const skipFeatures = setupType?.value === 'full';
38
+ const skipFeatures = setupType?.value === 'full' || setupType?.value === 'default';
39
39
  const mode = setupType?.value ?? 'full';
40
40
  const planFeatures = selectedFeatures?.map((item) => item.value) ?? [];
41
41
  const planSummary = stack === undefined ? '' : describeInstallPlan(stack, projectName, mode, planFeatures);
@@ -52,7 +52,7 @@ const App = ({ preselectedStack }) => {
52
52
  return orderedSteps;
53
53
  }
54
54
  // --- remaining questions (need the stack) ---
55
- orderedSteps.push(_jsx(InstallationMode, { onCompletion: finishStep, onSelect: onSelectSetupType }, `installation-mode-${attempt}`));
55
+ orderedSteps.push(_jsx(InstallationMode, { stack: stack, onCompletion: finishStep, onSelect: onSelectSetupType }, `installation-mode-${attempt}`));
56
56
  orderedSteps.push(_jsx(OptionalPackages, { stack: stack, onCompletion: finishStep, onSubmit: onSelectSelectedFeatures, skip: skipFeatures }, `optional-packages-${attempt}`));
57
57
  orderedSteps.push(_jsx(Confirmation, { summary: planSummary, onConfirm: finishStep, onCancel: restart }, `confirmation-${attempt}`));
58
58
  // --- operations (disk writes) ---
package/dist/cli.js CHANGED
@@ -16,7 +16,7 @@ const cli = meow(`
16
16
 
17
17
  Common options
18
18
  --name <string> Project name (alphanumeric, underscores)
19
- --mode <full|custom> Installation mode
19
+ --mode <full|default|custom> Installation mode (default mode is Canton-only)
20
20
  --features <list> Comma-separated features (with --mode=custom)
21
21
  EVM:
22
22
  demo Component demos and example pages
@@ -25,12 +25,13 @@ const cli = meow(`
25
25
  vocs Vocs documentation site
26
26
  husky Git hooks (Husky, lint-staged, commitlint)
27
27
  Canton:
28
- counter Counter demo dapp
29
- e2e Playwright end-to-end tests (requires counter)
28
+ github GitHub issue/PR templates and workflows
29
+ precommit Pre-commit hooks (Husky, lint-staged, commitlint)
30
30
  carpincho Carpincho browser-extension wallet
31
31
  llm LLM and agent artifacts (.claude, AGENTS.md, …)
32
- Dependencies are auto-resolved: requesting e2e
33
- also pulls in counter.
32
+ Canton's default mode keeps carpincho + llm and removes
33
+ github + precommit; full keeps everything; custom lets you
34
+ pick (github + precommit start unchecked).
34
35
  --non-interactive, --ni Run without prompts (auto-enabled when not a TTY)
35
36
  --info Output feature metadata as JSON (filter with --stack)
36
37
  --help Show this help
@@ -52,8 +53,8 @@ const cli = meow(`
52
53
  Interactive (prompts for stack and options):
53
54
  $ dappbooster
54
55
 
55
- Canton stack, full install (non-interactive):
56
- $ dappbooster --canton --ni --name my_dapp --mode full
56
+ Canton stack, recommended install (non-interactive):
57
+ $ dappbooster --canton --ni --name my_dapp --mode default
57
58
 
58
59
  EVM stack, custom install:
59
60
  $ dappbooster --evm --ni --name my_dapp --mode custom --features demo,subgraph
@@ -3,7 +3,7 @@ import { Text } from 'ink';
3
3
  import { useCallback, useEffect, useMemo, useState } from 'react';
4
4
  import { cleanupFiles } from '../../operations/index.js';
5
5
  import { completeInstall } from '../../operations/installGuard.js';
6
- import { deriveStepDisplay, getProjectFolder } from '../../utils/utils.js';
6
+ import { deriveStepDisplay, getProjectFolder, resolveModeFeatures } from '../../utils/utils.js';
7
7
  import Divider from '../Divider.js';
8
8
  const FileCleanup = ({ stack, onCompletion, installationConfig, projectName }) => {
9
9
  const { installationType, selectedFeatures } = installationConfig;
@@ -15,8 +15,10 @@ const FileCleanup = ({ stack, onCompletion, installationConfig, projectName }) =
15
15
  setSteps((prev) => [...prev, step]);
16
16
  }, []);
17
17
  useEffect(() => {
18
- const features = selectedFeatures?.map((f) => f.value) ?? [];
19
- cleanupFiles(stack, projectFolder, installationType ?? 'full', features, handleProgress)
18
+ const mode = installationType ?? 'full';
19
+ const selectedNames = selectedFeatures?.map((f) => f.value) ?? [];
20
+ const features = resolveModeFeatures(stack, mode, selectedNames);
21
+ cleanupFiles(stack, projectFolder, mode, features, handleProgress)
20
22
  .then(() => {
21
23
  // Scaffold is complete — an interrupt from here on must not delete the finished project.
22
24
  completeInstall();
@@ -3,7 +3,7 @@ import { Text } from 'ink';
3
3
  import { useCallback, useEffect, useMemo, useState } from 'react';
4
4
  import { createEnvFile } from '../../../operations/createEnvFile.js';
5
5
  import { installPackages } from '../../../operations/installPackages.js';
6
- import { deriveStepDisplay, getProjectFolder } from '../../../utils/utils.js';
6
+ import { deriveStepDisplay, getProjectFolder, resolveModeFeatures } from '../../../utils/utils.js';
7
7
  import Divider from '../../Divider.js';
8
8
  const Install = ({ stack, projectName, onCompletion, installationConfig }) => {
9
9
  const { installationType, selectedFeatures } = installationConfig;
@@ -18,11 +18,13 @@ const Install = ({ stack, projectName, onCompletion, installationConfig }) => {
18
18
  setSteps((prev) => [...prev, step]);
19
19
  }, []);
20
20
  useEffect(() => {
21
- const features = selectedFeatures?.map((f) => f.value) ?? [];
21
+ const mode = installationType ?? 'full';
22
+ const selectedNames = selectedFeatures?.map((f) => f.value) ?? [];
23
+ const features = resolveModeFeatures(stack, mode, selectedNames);
22
24
  const run = async () => {
23
25
  handleProgress('Creating env files');
24
26
  await createEnvFile(stack, projectFolder, features);
25
- await installPackages(stack, projectFolder, installationType ?? 'full', features, handleProgress);
27
+ await installPackages(stack, projectFolder, mode, features, handleProgress);
26
28
  };
27
29
  run()
28
30
  .then(() => {
@@ -1,6 +1,8 @@
1
1
  import { type FC } from 'react';
2
+ import { type Stack } from '../../constants/config.js';
2
3
  import type { InstallationSelectItem } from '../../types/types.js';
3
4
  interface Props {
5
+ stack: Stack;
4
6
  onCompletion: () => void;
5
7
  onSelect: (item: InstallationSelectItem) => void;
6
8
  }
@@ -3,19 +3,16 @@ import figures from 'figures';
3
3
  import { Text } from 'ink';
4
4
  import SelectInput from 'ink-select-input';
5
5
  import { useState } from 'react';
6
+ import { getInstallationModes } from '../../constants/config.js';
6
7
  import Divider from '../Divider.js';
7
- const installationTypeItems = [
8
- {
9
- label: 'Full',
10
- value: 'full',
11
- },
12
- {
13
- label: 'Custom',
14
- value: 'custom',
15
- },
16
- ];
17
- const InstallationMode = ({ onCompletion, onSelect }) => {
8
+ const MODE_LABELS = {
9
+ default: 'Default (recommended)',
10
+ full: 'Full',
11
+ custom: 'Custom',
12
+ };
13
+ const InstallationMode = ({ stack, onCompletion, onSelect }) => {
18
14
  const [selected, setSelected] = useState();
15
+ const installationTypeItems = getInstallationModes(stack).map((value) => ({ label: MODE_LABELS[value], value }));
19
16
  const handleSelect = (item) => {
20
17
  onSelect(item);
21
18
  setSelected(item);
@@ -13,7 +13,12 @@ const OptionalPackages = ({ stack, onCompletion, onSubmit, skip = false }) => {
13
13
  value: name,
14
14
  }));
15
15
  }, [stack]);
16
- // Keep the selection dependency-consistent as the user toggles (e.g. e2e requires counter).
16
+ // Pre-check only default:true features (e.g. Canton's github/precommit start unchecked).
17
+ const defaultSelected = useMemo(() => {
18
+ const features = getStackConfig(stack).features;
19
+ return customPackages.filter((pkg) => features[pkg.value]?.default);
20
+ }, [stack, customPackages]);
21
+ // Keep the selection dependency-consistent as the user toggles (resolves any feature `requires`).
17
22
  const transformSelection = useCallback((nextSelected, toggledItem, action) => {
18
23
  const selectedValues = nextSelected.map((item) => item.value);
19
24
  const resolved = applyFeatureToggle(stack, selectedValues, toggledItem.value, action);
@@ -38,6 +43,6 @@ const OptionalPackages = ({ stack, onCompletion, onSubmit, skip = false }) => {
38
43
  if (submitted) {
39
44
  return (_jsxs(Text, { children: ["Optional packages:", ' ', _jsx(Text, { bold: true, color: 'green', children: submitted.length > 0 ? submitted.map((item) => item.label).join(', ') : 'none' })] }));
40
45
  }
41
- return (_jsxs(_Fragment, { children: [_jsx(Text, { color: 'whiteBright', children: "Choose optional packages" }), _jsx(MultiSelect, { defaultSelected: customPackages, focus: true, items: customPackages, onSubmit: onHandleSubmit, transformSelection: transformSelection })] }));
46
+ return (_jsxs(_Fragment, { children: [_jsx(Text, { color: 'whiteBright', children: "Choose optional packages" }), _jsx(MultiSelect, { defaultSelected: defaultSelected, focus: true, items: customPackages, onSubmit: onHandleSubmit, transformSelection: transformSelection })] }));
42
47
  };
43
48
  export default OptionalPackages;
@@ -3,21 +3,20 @@ import figures from 'figures';
3
3
  import { Box, Text } from 'ink';
4
4
  import Link from 'ink-link';
5
5
  import { getStackConfig } from '../../constants/config.js';
6
- import { isFeatureSelected } from '../../utils/utils.js';
6
+ import { isFeatureSelected, resolveModeFeatures } from '../../utils/utils.js';
7
7
  import Divider from '../Divider.js';
8
8
  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."] })] }));
9
9
  const EvmPostInstallMessage = ({ 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" })] })] })] }));
10
- const CantonPostInstallMessage = ({ projectName, features, installationType }) => {
11
- const isFull = installationType === 'full';
12
- const counterEnabled = isFull || isFeatureSelected('counter', features);
13
- const carpinchoEnabled = isFull || isFeatureSelected('carpincho', features);
14
- return (_jsxs(Box, { flexDirection: 'column', rowGap: 1, paddingBottom: 2, children: [_jsx(Text, { color: 'whiteBright', children: "To start the Canton stack:" }), _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- Configure canton-barebones: ", _jsx(Text, { color: 'gray', children: "canton-barebones/.env" }), " was created from the example \u2014 review it."] }), _jsxs(Text, { children: ["3- Start the local Canton stack with ", _jsx(Text, { color: 'gray', children: "npm run canton:up" })] }), counterEnabled && (_jsxs(Text, { children: ["4- In a separate terminal, run the counter dapp:", ' ', _jsx(Text, { color: 'gray', children: "npm run app:dev" })] }))] }), carpinchoEnabled && (_jsx(Box, { alignItems: 'center', borderColor: 'yellow', borderStyle: 'bold', flexDirection: 'column', justifyContent: 'center', padding: 1, children: _jsxs(Text, { color: 'yellow', children: [figures.info, " ", _jsx(Text, { bold: true, children: "Carpincho Wallet" }), ": build it with", ' ', _jsx(Text, { color: 'gray', children: "npm run carpincho:build:extension" }), " and load", ' ', _jsx(Text, { color: 'gray', children: "carpincho-wallet/dist-extension" }), " as an unpacked browser extension ", figures.info] }) })), _jsx(Text, { children: "See the Canton stack README inside the project for full instructions." })] }));
10
+ const CantonPostInstallMessage = ({ projectName, features }) => {
11
+ const carpinchoEnabled = isFeatureSelected('carpincho', features);
12
+ return (_jsxs(Box, { flexDirection: 'column', rowGap: 1, paddingBottom: 2, children: [_jsx(Text, { color: 'whiteBright', children: "To start the Canton stack:" }), _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- Configure canton-barebones: ", _jsx(Text, { color: 'gray', children: "canton-barebones/.env" }), " was created from the example \u2014 review it."] }), _jsxs(Text, { children: ["3- Bring up the whole local stack with one command:", ' ', _jsx(Text, { color: 'gray', children: "./scripts/dev-stack.sh up" }), " (Docker must be running). Run", ' ', _jsx(Text, { color: 'gray', children: "./scripts/dev-stack.sh" }), " with no arguments for an interactive menu."] })] }), carpinchoEnabled && (_jsx(Box, { alignItems: 'center', borderColor: 'yellow', borderStyle: 'bold', flexDirection: 'column', justifyContent: 'center', padding: 1, children: _jsxs(Text, { color: 'yellow', children: [figures.info, " ", _jsx(Text, { bold: true, children: "Carpincho Wallet" }), ":", ' ', _jsx(Text, { color: 'gray', children: "./scripts/dev-stack.sh up" }), " also builds the extension and copies it to ", _jsx(Text, { color: 'gray', children: "~/Desktop/dist-extension" }), " \u2014 load it via", ' ', _jsx(Text, { color: 'gray', children: "chrome://extensions" }), " (Developer mode, Load unpacked)", ' ', figures.info] }) })), _jsxs(Box, { flexDirection: 'column', children: [_jsx(Text, { color: 'whiteBright', children: "Prefer to run the pieces by hand?" }), _jsxs(Text, { children: ["- Start the Canton stack with ", _jsx(Text, { color: 'gray', children: "npm run canton:up" }), ", then run the dapp frontend with ", _jsx(Text, { color: 'gray', children: "npm run app:dev" }), "."] }), carpinchoEnabled && (_jsxs(Text, { children: ["- Build the Carpincho extension with", ' ', _jsx(Text, { color: 'gray', children: "npm run carpincho:build:extension" }), " and load", ' ', _jsx(Text, { color: 'gray', children: "carpincho-wallet/dist-extension" }), " as an unpacked browser extension."] }))] }), _jsx(Text, { children: "See the Canton stack README inside the project for full instructions." })] }));
15
13
  };
16
14
  const PostInstall = ({ stack, installationConfig, projectName }) => {
17
15
  const { selectedFeatures, installationType } = installationConfig;
18
- const features = selectedFeatures?.map((f) => f.value) ?? [];
16
+ const selectedNames = selectedFeatures?.map((f) => f.value) ?? [];
17
+ const features = resolveModeFeatures(stack, installationType ?? 'full', selectedNames);
19
18
  const stackLabel = getStackConfig(stack).label;
20
19
  return (_jsxs(_Fragment, { children: [_jsx(Divider, { title: `Post-install instructions — ${stackLabel}` }), _jsxs(Box, { flexDirection: 'column', rowGap: 2, children: [stack === 'evm' &&
21
- (isFeatureSelected('subgraph', features) || installationType === 'full') && (_jsx(SubgraphWarningMessage, {})), stack === 'evm' && _jsx(EvmPostInstallMessage, { projectName: projectName }), stack === 'canton' && (_jsx(CantonPostInstallMessage, { projectName: projectName, features: features, installationType: installationType }))] })] }));
20
+ (isFeatureSelected('subgraph', features) || installationType === 'full') && (_jsx(SubgraphWarningMessage, {})), stack === 'evm' && _jsx(EvmPostInstallMessage, { projectName: projectName }), stack === 'canton' && (_jsx(CantonPostInstallMessage, { projectName: projectName, features: features }))] })] }));
22
21
  };
23
22
  export default PostInstall;
@@ -1,3 +1,4 @@
1
+ import type { InstallationType } from '../types/types.js';
1
2
  export type Stack = 'evm' | 'canton';
2
3
  export type RefType = 'tag-latest' | 'branch';
3
4
  export type PackageManager = 'pnpm' | 'npm';
@@ -24,6 +25,7 @@ export type StackConfig = {
24
25
  ref?: string;
25
26
  packageManager: PackageManager;
26
27
  removeAfterClone: string[];
28
+ postInstall?: string[];
27
29
  envFiles: EnvFile[];
28
30
  features: Record<FeatureName, FeatureDefinition>;
29
31
  };
@@ -33,3 +35,5 @@ export declare function getStackConfig(stack: Stack): StackConfig;
33
35
  export declare function getFeatureNames(stack: Stack): FeatureName[];
34
36
  export declare function isFeatureNameValid(stack: Stack, name: string): boolean;
35
37
  export declare function isStackName(name: string): name is Stack;
38
+ export declare function getDefaultFeatureNames(stack: Stack): FeatureName[];
39
+ export declare function getInstallationModes(stack: Stack): InstallationType[];
@@ -65,13 +65,14 @@ export const stackDefinitions = {
65
65
  ref: 'main',
66
66
  packageManager: 'npm',
67
67
  removeAfterClone: [],
68
+ postInstall: [
69
+ 'Review canton-barebones/.env (created from the example)',
70
+ 'Run ./scripts/dev-stack.sh up to bring up the whole local stack in one command — Docker must be running (run ./scripts/dev-stack.sh with no arguments for an interactive menu)',
71
+ 'Fallback — start each piece manually: npm run canton:up for the Canton stack, then npm run app:dev for the dapp frontend',
72
+ ],
68
73
  envFiles: [
69
74
  { from: 'canton-barebones/.env.example', to: 'canton-barebones/.env' },
70
- {
71
- from: 'counter/frontend/.env.local.example',
72
- to: 'counter/frontend/.env.local',
73
- ifFeature: 'counter',
74
- },
75
+ { from: 'dapp/frontend/.env.local.example', to: 'dapp/frontend/.env.local' },
75
76
  {
76
77
  from: 'carpincho-wallet/.env.local.example',
77
78
  to: 'carpincho-wallet/.env.local',
@@ -79,25 +80,19 @@ export const stackDefinitions = {
79
80
  },
80
81
  ],
81
82
  features: {
82
- counter: {
83
- description: 'Counter demo dapp (frontend + Daml + wallet-service)',
84
- label: 'Counter demo',
83
+ github: {
84
+ description: 'GitHub issue/PR templates and workflows (.github)',
85
+ label: 'GitHub templates & workflows',
85
86
  packages: [],
86
- default: true,
87
- paths: ['counter'],
88
- postInstall: [
89
- 'Review canton-barebones/.env (created from the example)',
90
- 'Run npm run canton:up to start the local Canton stack',
91
- 'Run npm run app:dev to start the counter dapp frontend',
92
- ],
87
+ default: false,
88
+ paths: ['.github'],
93
89
  },
94
- e2e: {
95
- description: 'Playwright end-to-end test suite (drives the counter dapp)',
96
- label: 'E2E tests',
90
+ precommit: {
91
+ description: 'Pre-commit hooks (Husky, lint-staged, commitlint)',
92
+ label: 'Pre-commit hooks',
97
93
  packages: [],
98
- default: true,
99
- paths: ['e2e'],
100
- requires: ['counter'],
94
+ default: false,
95
+ paths: ['.husky', '.lintstagedrc.mjs', 'commitlint.config.js'],
101
96
  },
102
97
  carpincho: {
103
98
  description: 'Carpincho browser-extension wallet (frontend + build tooling)',
@@ -106,8 +101,8 @@ export const stackDefinitions = {
106
101
  default: true,
107
102
  paths: ['carpincho-wallet'],
108
103
  postInstall: [
109
- 'Build the Carpincho extension with npm run carpincho:build:extension',
110
- 'Load carpincho-wallet/dist-extension as an unpacked browser extension',
104
+ './scripts/dev-stack.sh up also builds the Carpincho extension and copies it to ~/Desktop/dist-extension (load it via chrome://extensions, Developer mode -> Load unpacked)',
105
+ 'Fallback — build it manually with npm run carpincho:build:extension, then load carpincho-wallet/dist-extension as an unpacked browser extension',
111
106
  ],
112
107
  },
113
108
  llm: {
@@ -153,3 +148,15 @@ export function isFeatureNameValid(stack, name) {
153
148
  export function isStackName(name) {
154
149
  return stackNames.includes(name);
155
150
  }
151
+ export function getDefaultFeatureNames(stack) {
152
+ return Object.entries(stackDefinitions[stack].features)
153
+ .filter(([, definition]) => definition.default)
154
+ .map(([name]) => name);
155
+ }
156
+ // Available installation modes per stack. `default` (keep only default:true features) is offered
157
+ // only when the stack has at least one opt-out (default:false) feature; otherwise it would be
158
+ // identical to `full`. Today that means Canton only (EVM's features are all default:true).
159
+ export function getInstallationModes(stack) {
160
+ const hasOptOutFeature = getDefaultFeatureNames(stack).length < getFeatureNames(stack).length;
161
+ return hasOptOutFeature ? ['default', 'full', 'custom'] : ['full', 'custom'];
162
+ }
package/dist/info.js CHANGED
@@ -37,6 +37,7 @@ export function getInfoOutput(stackFilter) {
37
37
  stacks,
38
38
  modes: {
39
39
  full: 'Install all features',
40
+ default: 'Install the recommended set (Canton only; for EVM this equals full)',
40
41
  custom: 'Choose features individually',
41
42
  },
42
43
  }, null, 2);
@@ -2,7 +2,7 @@ import process from 'node:process';
2
2
  import { getFeatureNames, isFeatureNameValid, isStackName, stackNames, } from './constants/config.js';
3
3
  import { cleanupFiles, cloneRepo, createEnvFile, installPackages } from './operations/index.js';
4
4
  import { beginInstall, completeInstall } from './operations/installGuard.js';
5
- import { getPostInstallMessages, getProjectFolder, isValidName, projectDirectoryExists, resolveSelectedFeatures, } from './utils/utils.js';
5
+ import { getPostInstallMessages, getProjectFolder, isValidName, projectDirectoryExists, resolveModeFeatures, } from './utils/utils.js';
6
6
  function fail(error) {
7
7
  const result = { success: false, error };
8
8
  console.log(JSON.stringify(result, null, 2));
@@ -40,14 +40,22 @@ function validate(flags) {
40
40
  if (!isValidName(flags.name)) {
41
41
  fail('Invalid project name: only letters, numbers, and underscores are allowed');
42
42
  }
43
- if (flags.mode !== 'full' && flags.mode !== 'custom') {
44
- fail("Invalid mode: must be 'full' or 'custom'");
43
+ if (flags.mode !== 'full' && flags.mode !== 'default' && flags.mode !== 'custom') {
44
+ fail("Invalid mode: must be 'full', 'default', or 'custom'");
45
45
  }
46
- if (flags.mode === 'full') {
46
+ if (flags.mode === 'default' && stack === 'evm') {
47
+ fail("Invalid mode: 'default' is only available for the canton stack");
48
+ }
49
+ if (flags.mode === 'full' || flags.mode === 'default') {
47
50
  if (projectDirectoryExists(flags.name)) {
48
51
  fail(`Project directory '${flags.name}' already exists`);
49
52
  }
50
- return { stack, name: flags.name, mode: flags.mode, features: getFeatureNames(stack) };
53
+ return {
54
+ stack,
55
+ name: flags.name,
56
+ mode: flags.mode,
57
+ features: resolveModeFeatures(stack, flags.mode),
58
+ };
51
59
  }
52
60
  if (!flags.features) {
53
61
  fail('--mode custom requires --features. Use --info to see available features.');
@@ -68,7 +76,7 @@ function validate(flags) {
68
76
  stack,
69
77
  name: flags.name,
70
78
  mode: flags.mode,
71
- features: resolveSelectedFeatures(stack, features),
79
+ features: resolveModeFeatures(stack, 'custom', features),
72
80
  };
73
81
  }
74
82
  export async function runNonInteractive(flags) {
@@ -29,32 +29,37 @@ function removePackageKeys(packageBlock, keys) {
29
29
  }
30
30
  return changed;
31
31
  }
32
+ // Strip the husky/lint-staged/commitlint tooling scripts and dependencies from a parsed
33
+ // package.json (mutates in place). Returns whether anything was removed.
34
+ function stripToolingEntries(packageJson, scripts) {
35
+ let changed = false;
36
+ if (scripts) {
37
+ for (const scriptName of TOOLING_SCRIPTS_TO_REMOVE) {
38
+ if (scripts[scriptName] !== undefined) {
39
+ scripts[scriptName] = undefined;
40
+ changed = true;
41
+ }
42
+ }
43
+ }
44
+ const dependencyGroups = [
45
+ packageJson.dependencies,
46
+ packageJson.devDependencies,
47
+ packageJson.optionalDependencies,
48
+ packageJson.peerDependencies,
49
+ ];
50
+ for (const group of dependencyGroups) {
51
+ if (removePackageKeys(group, TOOLING_PACKAGES_TO_REMOVE)) {
52
+ changed = true;
53
+ }
54
+ }
55
+ return changed;
56
+ }
32
57
  function sanitizeRepositoryPackageJson(projectFolder) {
33
58
  const packageJsonPath = resolve(projectFolder, 'package.json');
34
59
  try {
35
60
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
36
61
  const scripts = packageJson.scripts;
37
- let changed = false;
38
- if (scripts) {
39
- for (const scriptName of TOOLING_SCRIPTS_TO_REMOVE) {
40
- if (scripts[scriptName] !== undefined) {
41
- scripts[scriptName] = undefined;
42
- changed = true;
43
- }
44
- }
45
- }
46
- const dependencyGroups = [
47
- packageJson.dependencies,
48
- packageJson.devDependencies,
49
- packageJson.optionalDependencies,
50
- packageJson.peerDependencies,
51
- ];
52
- for (const group of dependencyGroups) {
53
- if (removePackageKeys(group, TOOLING_PACKAGES_TO_REMOVE)) {
54
- changed = true;
55
- }
56
- }
57
- if (changed) {
62
+ if (stripToolingEntries(packageJson, scripts)) {
58
63
  writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
59
64
  }
60
65
  }
@@ -67,10 +72,11 @@ async function removePaths(projectFolder, relativePaths) {
67
72
  await rm(resolve(projectFolder, relativePath), { recursive: true, force: true });
68
73
  }
69
74
  }
70
- async function cleanupRepositoryHygiene(stack, projectFolder, onProgress) {
75
+ // EVM-only hygiene: always strip CI metadata, agent metadata, and git automation. Canton models
76
+ // .github and pre-commit hooks as optional features instead (see cleanupCantonFiles).
77
+ async function cleanupRepositoryHygiene(projectFolder, onProgress) {
71
78
  onProgress?.('Repository metadata');
72
- const metadataPaths = stack === 'evm' ? [...EVM_METADATA_PATHS, ...CI_PATHS] : CI_PATHS;
73
- await removePaths(projectFolder, metadataPaths);
79
+ await removePaths(projectFolder, [...EVM_METADATA_PATHS, ...CI_PATHS]);
74
80
  onProgress?.('Git hooks and commit linting');
75
81
  await removePaths(projectFolder, AUTOMATION_PATHS);
76
82
  sanitizeRepositoryPackageJson(projectFolder);
@@ -102,35 +108,36 @@ function patchPackageJsonEvm(projectFolder, features) {
102
108
  scripts['commitlint:ci'] = undefined;
103
109
  writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
104
110
  }
105
- // Strip scripts by what they run (e.g. `npm --prefix counter/frontend ...`)
111
+ // Strip scripts by what they run (e.g. `npm --prefix carpincho-wallet ...`)
106
112
  // rather than by name, so cleanup tracks directory removal even as scripts change.
107
113
  function scriptTargetsRemovedDir(command, removedDirs) {
108
114
  const tokens = command.split(/\s+/);
109
115
  return removedDirs.some((dir) => tokens.some((token) => token === dir || token.startsWith(`${dir}/`)));
110
116
  }
111
- function patchPackageJsonCanton(projectFolder, removedDirs) {
117
+ function patchPackageJsonCanton(projectFolder, removedDirs, precommitRemoved) {
112
118
  const packageJsonPath = resolve(projectFolder, 'package.json');
113
119
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
114
120
  const scripts = packageJson.scripts;
115
- if (!scripts) {
116
- writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
117
- return;
118
- }
119
- for (const [name, command] of Object.entries(scripts)) {
120
- if (command !== undefined && scriptTargetsRemovedDir(command, removedDirs)) {
121
- scripts[name] = undefined;
121
+ if (scripts) {
122
+ for (const [name, command] of Object.entries(scripts)) {
123
+ if (command !== undefined && scriptTargetsRemovedDir(command, removedDirs)) {
124
+ scripts[name] = undefined;
125
+ }
122
126
  }
123
127
  }
124
- // biome-ignore lint/complexity/useLiteralKeys: TS index-signature compatibility in strict mode
125
- scripts['prepare'] = undefined;
126
- // biome-ignore lint/complexity/useLiteralKeys: TS index-signature compatibility in strict mode
127
- scripts['commitlint'] = undefined;
128
- scripts['commitlint:check'] = undefined;
129
- scripts['commitlint:ci'] = undefined;
128
+ // The husky tooling (prepare/commitlint scripts + husky/lint-staged/commitlint deps) only leaves
129
+ // with the pre-commit feature.
130
+ if (precommitRemoved) {
131
+ stripToolingEntries(packageJson, scripts);
132
+ }
130
133
  writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
131
134
  }
132
135
  async function createInitialCommit(projectFolder) {
133
136
  await execFile('git', ['add', '.'], { cwd: projectFolder });
137
+ // --no-verify: the scaffold's baseline commit must not run the project's own git hooks. When the
138
+ // `precommit` feature is kept, husky's pre-commit/commit-msg hooks are present and would lint and
139
+ // test the freshly-cloned tree (and fail), blocking the install. The user's later commits still
140
+ // run hooks normally.
134
141
  await execFile('git', [
135
142
  '-c',
136
143
  'user.name=dAppBooster',
@@ -139,6 +146,7 @@ async function createInitialCommit(projectFolder) {
139
146
  '-c',
140
147
  'commit.gpgsign=false',
141
148
  'commit',
149
+ '--no-verify',
142
150
  '-m',
143
151
  'chore: initial commit',
144
152
  ], { cwd: projectFolder });
@@ -190,10 +198,11 @@ async function cleanupEvmFiles(projectFolder, mode, features, onProgress) {
190
198
  }
191
199
  async function cleanupCantonFiles(projectFolder, mode, features, onProgress) {
192
200
  const cantonFeatures = getStackConfig('canton').features;
193
- // Each deselected feature contributes its paths to removal (custom mode only). Directory paths
194
- // also feed script stripping, so a removed feature's package.json scripts disappear with it.
201
+ // Each deselected feature contributes its paths to removal. `default` and `custom` remove;
202
+ // `full` keeps everything. Directory paths also feed script stripping, so a removed feature's
203
+ // package.json scripts disappear with it.
195
204
  const removedDirs = [];
196
- if (mode === 'custom') {
205
+ if (mode !== 'full') {
197
206
  for (const [name, definition] of Object.entries(cantonFeatures)) {
198
207
  if (isFeatureSelected(name, features) || !definition.paths || definition.paths.length === 0) {
199
208
  continue;
@@ -203,15 +212,16 @@ async function cleanupCantonFiles(projectFolder, mode, features, onProgress) {
203
212
  removedDirs.push(...definition.paths);
204
213
  }
205
214
  }
206
- patchPackageJsonCanton(projectFolder, removedDirs);
215
+ const precommitRemoved = mode !== 'full' && !isFeatureSelected('precommit', features);
216
+ patchPackageJsonCanton(projectFolder, removedDirs, precommitRemoved);
207
217
  }
208
218
  export async function cleanupFiles(stack, projectFolder, mode, features = [], onProgress) {
209
- await cleanupRepositoryHygiene(stack, projectFolder, onProgress);
210
219
  if (stack === 'canton') {
211
220
  await cleanupCantonFiles(projectFolder, mode, features, onProgress);
212
221
  onProgress?.('Initial commit');
213
222
  await createInitialCommit(projectFolder);
214
223
  return;
215
224
  }
225
+ await cleanupRepositoryHygiene(projectFolder, onProgress);
216
226
  await cleanupEvmFiles(projectFolder, mode, features, onProgress);
217
227
  }
@@ -3,7 +3,7 @@ export type SelectItem = {
3
3
  value: string;
4
4
  };
5
5
  export type MultiSelectItem = SelectItem;
6
- export type InstallationType = 'full' | 'custom';
6
+ export type InstallationType = 'full' | 'default' | 'custom';
7
7
  export type InstallationSelectItem = {
8
8
  label: string;
9
9
  value: InstallationType;
@@ -1,4 +1,5 @@
1
1
  import { type FeatureName, type Stack } from '../constants/config.js';
2
+ import type { InstallationType } from '../types/types.js';
2
3
  export declare function getProjectFolder(projectName: string): string;
3
4
  export declare function isValidName(name: string): boolean;
4
5
  export declare function isAnswerConfirmed(answer?: string, errorMessage?: string): boolean;
@@ -7,9 +8,10 @@ export declare function isFeatureSelected(feature: FeatureName, selectedFeatures
7
8
  type FeatureToggleAction = 'select' | 'unselect';
8
9
  export declare function resolveSelectedFeatures(stack: Stack, selectedFeatures: FeatureName[]): FeatureName[];
9
10
  export declare function applyFeatureToggle(stack: Stack, selectedFeatures: FeatureName[], toggledFeature: FeatureName, action: FeatureToggleAction): FeatureName[];
10
- export declare function describeInstallPlan(stack: Stack, projectName: string, mode: 'full' | 'custom', selectedFeatures: FeatureName[]): string;
11
+ export declare function describeInstallPlan(stack: Stack, projectName: string, mode: InstallationType, selectedFeatures: FeatureName[]): string;
11
12
  export declare function getPackagesToRemove(stack: Stack, selectedFeatures: FeatureName[]): string[];
12
- export declare function getPostInstallMessages(stack: Stack, mode: 'full' | 'custom', selectedFeatures: FeatureName[]): string[];
13
+ export declare function getPostInstallMessages(stack: Stack, mode: InstallationType, selectedFeatures: FeatureName[]): string[];
14
+ export declare function resolveModeFeatures(stack: Stack, mode: InstallationType, customSelection?: FeatureName[]): FeatureName[];
13
15
  export declare function projectDirectoryExists(projectName: string): boolean;
14
16
  type StepStatus = 'running' | 'done' | 'error';
15
17
  type StepDisplay = {
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import process from 'node:process';
4
- import { getFeatureNames, getStackConfig, } from '../constants/config.js';
4
+ import { getDefaultFeatureNames, getFeatureNames, getStackConfig, } from '../constants/config.js';
5
5
  export function getProjectFolder(projectName) {
6
6
  return join(process.cwd(), projectName);
7
7
  }
@@ -68,6 +68,9 @@ export function describeInstallPlan(stack, projectName, mode, selectedFeatures)
68
68
  if (mode === 'full') {
69
69
  return `${head} · Mode: full (all features)`;
70
70
  }
71
+ if (mode === 'default') {
72
+ return `${head} · Mode: default (recommended)`;
73
+ }
71
74
  const features = selectedFeatures.length > 0 ? selectedFeatures.join(', ') : 'none';
72
75
  return `${head} · Mode: custom · Features: ${features}`;
73
76
  }
@@ -78,11 +81,24 @@ export function getPackagesToRemove(stack, selectedFeatures) {
78
81
  .flatMap(([, def]) => def.packages);
79
82
  }
80
83
  export function getPostInstallMessages(stack, mode, selectedFeatures) {
81
- const features = getStackConfig(stack).features;
84
+ const config = getStackConfig(stack);
85
+ const features = config.features;
86
+ const stackLevel = config.postInstall ?? [];
87
+ const kept = resolveModeFeatures(stack, mode, selectedFeatures);
88
+ const featureMessages = kept.flatMap((name) => features[name]?.postInstall ?? []);
89
+ return [...stackLevel, ...featureMessages];
90
+ }
91
+ // Resolves the kept-feature list for a mode: full → all, default → default:true set,
92
+ // custom → the user's selection (transitive requires resolved). Shared by the non-interactive
93
+ // path and the interactive Install/FileCleanup/PostInstall steps.
94
+ export function resolveModeFeatures(stack, mode, customSelection = []) {
82
95
  if (mode === 'full') {
83
- return Object.values(features).flatMap((def) => def.postInstall ?? []);
96
+ return getFeatureNames(stack);
97
+ }
98
+ if (mode === 'default') {
99
+ return getDefaultFeatureNames(stack);
84
100
  }
85
- return selectedFeatures.flatMap((name) => features[name]?.postInstall ?? []);
101
+ return resolveSelectedFeatures(stack, customSelection);
86
102
  }
87
103
  export function projectDirectoryExists(projectName) {
88
104
  return existsSync(getProjectFolder(projectName));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dappbooster",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
4
4
  "description": "Agent-friendly dAppBooster installer that scaffolds Web3 dApps via TUI or non-interactive CLI/CI.",
5
5
  "keywords": [
6
6
  "dappbooster",
@@ -39,9 +39,7 @@
39
39
  "lint": "pnpm biome check",
40
40
  "lint:fix": "pnpm biome check --write"
41
41
  },
42
- "files": [
43
- "dist"
44
- ],
42
+ "files": ["dist"],
45
43
  "dependencies": {
46
44
  "figures": "^6.1.0",
47
45
  "ink": "^5.2.1",
package/readme.md CHANGED
@@ -7,7 +7,7 @@ agents.
7
7
 
8
8
  - **EVM** — the original [dAppBooster](https://dappbooster.dev/) for Ethereum, Polygon, Base, and
9
9
  other EVM chains.
10
- - **Canton** — [dAppBooster for Canton](https://dappbooster-canton-landing.vercel.app/): Daml
10
+ - **Canton** — [dAppBooster for Canton](https://www.dappbooster.cc/): Daml
11
11
  ledger, Carpincho wallet, off-chain services.
12
12
 
13
13
  ## Choose your stack
@@ -33,8 +33,9 @@ Omit the flag to be prompted for the stack in the wizard. Jump to the [EVM stack
33
33
  pnpm dlx dappbooster
34
34
  ```
35
35
 
36
- The wizard prompts for stack → project name → mode (full / custom) → features, then clones,
37
- installs, cleans up, and prints next steps. Pass `--evm` or `--canton` to skip the stack prompt.
36
+ The wizard prompts for stack → project name → mode (Canton offers default / full / custom; EVM
37
+ offers full / custom) → features, then clones, installs, cleans up, and prints next steps. Pass
38
+ `--evm` or `--canton` to skip the stack prompt.
38
39
 
39
40
  dAppBooster documentation: https://docs.dappbooster.dev/
40
41
 
@@ -55,7 +56,7 @@ pnpm dlx dappbooster --info --stack canton # filter to one stack (or --info --
55
56
  | `--canton` / `--evm` | Pick the stack (mutually exclusive shortcuts) |
56
57
  | `--stack <evm\|canton>` | Pick the stack by name (useful when scripting) |
57
58
  | `--name <name>` | Project directory name (`/^[a-zA-Z0-9_]+$/`) |
58
- | `--mode <full\|custom>` | `full` installs every feature; `custom` needs `--features` |
59
+ | `--mode <full\|default\|custom>` | `default` (Canton only) keeps the recommended set; `full` installs every feature; `custom` needs `--features` |
59
60
  | `--features <a,b,c>` | Comma-separated feature keys (custom mode only) |
60
61
  | `--ni` | Force non-interactive mode |
61
62
 
@@ -65,7 +66,7 @@ accepts only its own feature keys, and validation errors name the stack:
65
66
  ```json
66
67
  {
67
68
  "success": false,
68
- "error": "Unknown features for stack 'canton': subgraph. Valid features: counter, e2e, carpincho, llm"
69
+ "error": "Unknown features for stack 'canton': subgraph. Valid features: github, precommit, carpincho, llm"
69
70
  }
70
71
  ```
71
72
 
@@ -79,7 +80,7 @@ A successful install prints:
79
80
  "success": true,
80
81
  "stack": "evm|canton",
81
82
  "projectName": "...",
82
- "mode": "full|custom",
83
+ "mode": "full|default|custom",
83
84
  "features": ["..."],
84
85
  "path": "/absolute/path",
85
86
  "postInstall": ["..."]
@@ -131,51 +132,63 @@ pnpm dlx dappbooster --canton
131
132
  Interactive (skips the stack prompt) or non-interactive:
132
133
 
133
134
  ```shell
134
- pnpm dlx dappbooster --canton --ni --name my_canton_dapp --mode full
135
- pnpm dlx dappbooster --canton --ni --name my_canton --mode custom --features counter,carpincho
135
+ pnpm dlx dappbooster --canton --ni --name my_canton_dapp --mode default
136
+ pnpm dlx dappbooster --canton --ni --name my_canton --mode custom --features carpincho,github
136
137
  ```
137
138
 
138
139
  | Feature | Key | Default | Description |
139
140
  |---|---|---|---|
140
- | Counter demo | `counter` | | Counter demo dapp (frontend + Daml + wallet-service) |
141
- | E2E tests | `e2e` || Playwright end-to-end test suite (**requires `counter`**) |
141
+ | GitHub templates & workflows | `github` | | GitHub issue/PR templates and workflows (`.github`) |
142
+ | Pre-commit hooks | `precommit` | | Husky, lint-staged, and commitlint |
142
143
  | Carpincho wallet | `carpincho` | ✓ | Carpincho browser-extension wallet (frontend + build tooling) |
143
144
  | LLM & agent artifacts | `llm` | ✓ | `.claude`, `AGENTS.md`, `CLAUDE.md`, `architecture.md`, `llms.txt`, … |
144
145
 
145
- `e2e` drives the counter dapp, so it **requires** `counter`: requesting `--features e2e` auto-pulls
146
- `counter` in (the success JSON reports `["counter", "e2e"]`), and in the wizard, deselecting
147
- `counter` also unchecks `e2e`.
146
+ `default` mode (the recommended Canton install) keeps `carpincho` + `llm` and removes `github` +
147
+ `precommit`; `full` keeps all four; `custom` lets you pick (in the wizard `github` and `precommit`
148
+ start unchecked). To remove the demo features (`counter`, `sign-message`) after scaffolding, follow
149
+ the "Removing a feature" guide in the generated `dapp/frontend/README.md` — the installer never
150
+ deletes demo source itself.
148
151
 
149
152
  The Canton scaffold uses **npm** (a property of the generated project, not this installer). After
150
- install: review `canton-barebones/.env`, run `npm run canton:up` to start the local Canton stack,
151
- and `npm run app:dev` to run the counter dapp frontend. When `carpincho` is included, build the
152
- extension with `npm run carpincho:build:extension` and load `carpincho-wallet/dist-extension` as an
153
- unpacked browser extension.
153
+ install, review `canton-barebones/.env`, then bring the whole local stack up with a single command:
154
+ `./scripts/dev-stack.sh up` (Docker must be running). It starts the Canton + Postgres +
155
+ wallet-service containers, runs the health checks, builds and deploys the quickstart-counter DAR,
156
+ launches the dapp frontend (`:3012`), and — when `carpincho` is included — builds the Carpincho
157
+ extension and copies it to `~/Desktop/dist-extension` (load it via `chrome://extensions`, Developer
158
+ mode → Load unpacked). Run `./scripts/dev-stack.sh` with no arguments for an interactive arrow-key
159
+ menu; `mock-up` brings up a Docker-free mocked wallet-service + Carpincho web app, and `down` tears
160
+ everything back down.
161
+
162
+ Prefer to run the pieces by hand? The underlying npm scripts still work: `npm run canton:up` to
163
+ start the local Canton stack and `npm run app:dev` for the dapp frontend, and when `carpincho` is
164
+ included build the extension with `npm run carpincho:build:extension` and load
165
+ `carpincho-wallet/dist-extension` as an unpacked browser extension.
154
166
 
155
167
  **What gets stripped:**
156
168
 
157
- - **Always** (every stack and mode): CI config (`.github`) and the husky/commitlint automation
158
- (`.husky`, `.lintstagedrc.mjs`, `commitlint.config.js`), plus their entries in the root
159
- `package.json`.
160
- - **Per feature** (custom mode): deselecting a feature removes its files and any `package.json`
161
- scripts that target them deselecting `carpincho` removes `carpincho-wallet/` and its scripts
162
- (`wallet:dev`, `carpincho:build:extension`); deselecting `llm` removes the agent docs.
163
- - A **full** install keeps all four features including `carpincho-wallet/` and the agent docs.
169
+ - **EVM** always removes CI config (`.github`) and the husky/commitlint automation as hygiene.
170
+ - **Canton** treats `.github` and pre-commit hooks as optional features: `default` mode removes
171
+ both; `full` keeps both; `custom` removes whichever you uncheck. Deselecting `carpincho` removes
172
+ `carpincho-wallet/` and its scripts (`wallet:dev`, `carpincho:build:extension`); deselecting `llm`
173
+ removes the agent docs. Removing `precommit` also strips the `prepare` script and the
174
+ husky/lint-staged/commitlint dev-dependencies from the root `package.json`.
175
+ - The Canton installer never deletes demo source (the `counter`/`sign-message` features) that is
176
+ user-controlled via the template's `dapp/frontend/README.md`.
164
177
 
165
178
  ```json
166
179
  {
167
180
  "success": true,
168
181
  "stack": "canton",
169
182
  "projectName": "my_canton_dapp",
170
- "mode": "full",
171
- "features": ["counter", "e2e", "carpincho", "llm"],
183
+ "mode": "default",
184
+ "features": ["carpincho", "llm"],
172
185
  "path": "/absolute/path/to/my_canton_dapp",
173
186
  "postInstall": [
174
187
  "Review canton-barebones/.env (created from the example)",
175
- "Run npm run canton:up to start the local Canton stack",
176
- "Run npm run app:dev to start the counter dapp frontend",
177
- "Build the Carpincho extension with npm run carpincho:build:extension",
178
- "Load carpincho-wallet/dist-extension as an unpacked browser extension"
188
+ "Run ./scripts/dev-stack.sh up to bring up the whole local stack in one command — Docker must be running (run ./scripts/dev-stack.sh with no arguments for an interactive menu)",
189
+ "Fallback — start each piece manually: npm run canton:up for the Canton stack, then npm run app:dev for the dapp frontend",
190
+ "./scripts/dev-stack.sh up also builds the Carpincho extension and copies it to ~/Desktop/dist-extension (load it via chrome://extensions, Developer mode -> Load unpacked)",
191
+ "Fallback — build it manually with npm run carpincho:build:extension, then load carpincho-wallet/dist-extension as an unpacked browser extension"
179
192
  ]
180
193
  }
181
194
  ```