Skip to content

Commit 2af6518

Browse files
authored
Feat/init template improvements (#12)
* feat(init): improve generated scaffold ergonomics * fix(task): use bumped version for release commits * chore: release v1.2.0
1 parent e60e651 commit 2af6518

20 files changed

Lines changed: 582 additions & 112 deletions

‎README.md‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ wfkit init --name my-site
4040
```
4141

4242
Add `--init-git` if you want the scaffold to initialize a local repository immediately.
43+
Add `--skip-install` if you only want the files and do not want to install dependencies yet.
44+
Use `--force` if you explicitly want to write scaffold files into an existing non-empty directory.
4345

4446
This creates a new folder in your current directory:
4547

@@ -49,6 +51,7 @@ my-site/
4951

5052
Inside it, `wfkit` generates:
5153

54+
- `README.md`
5255
- `package.json`
5356
- `wfkit.json`
5457
- `vite.config.js`
@@ -85,6 +88,8 @@ The generated project is organized for Webflow:
8588

8689
```text
8790
src/
91+
features/
92+
site-status.ts
8893
global/
8994
index.ts
9095
modules/
@@ -94,6 +99,7 @@ src/
9499
index.ts
95100
utils/
96101
dom.ts
102+
webflow.ts
97103
build/
98104
webflow-vite-plugin.js
99105
docs/
@@ -105,9 +111,11 @@ dist/assets/
105111
How it works:
106112

107113
- `src/global/index.ts` is the one explicit global entry
114+
- `src/features/*` is for reusable behaviors that can be mounted from global or page entries
108115
- `src/global/modules/*` contains reusable global behaviors that you choose to import from the global entry
109116
- `src/pages/*/index.ts` is for per-page code
110-
- `src/utils/*` is shared code that can be used from both global and page entries
117+
- `src/utils/dom.ts` contains low-level DOM helpers
118+
- `src/utils/webflow.ts` contains DRY mount helpers that align page and feature bootstrapping with the Webflow runtime
111119
- `docs/index.md` is the default markdown entry for the docs hub page
112120
- `dist/assets/wfkit-manifest.json` is generated during build and used by `wfkit` to resolve the correct output files
113121

@@ -157,7 +165,7 @@ bun install
157165
Then:
158166

159167
```bash
160-
bun run start
168+
bun run dev
161169
```
162170

163171
This starts:
@@ -310,14 +318,17 @@ This checks:
310318

311319
Create a new project scaffold.
312320

321+
The scaffold is TypeScript-first and installs dependencies by default unless you pass `--skip-install`.
322+
313323
Options:
314324

315325
- `--name` Project name
316326
- `--pages-dir` Directory for page scripts
317327
- `--global-entry` Global entry file
318328
- `--global-var` Global variable name
319329
- `--init-git` Initialize a local git repository
320-
- `--types` Generate TypeScript types
330+
- `--skip-install` Skip dependency installation after generating the scaffold
331+
- `--force` Allow writing scaffold files into an existing non-empty directory
321332
- `--package-manager` Package manager: `bun`, `npm`, `yarn`, `pnpm`
322333

323334
### `wfkit proxy`

‎Taskfile.yml‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,6 @@ tasks:
8787

8888
release:prepare:
8989
desc: "Bump the release version and create a release commit. Usage: task release:prepare TYPE=patch"
90-
vars:
91-
RELEASE_VERSION:
92-
sh: node -p "require('./npm/package.json').version"
9390
preconditions:
9491
- sh: test -n "{{.TYPE}}"
9592
msg: "TYPE is required. Use patch, minor, major, or an exact semver version."
@@ -99,7 +96,7 @@ tasks:
9996
- task: version:bump
10097
vars: { TYPE: "{{.TYPE}}" }
10198
- git add npm/package.json
102-
- 'git commit -m "chore: release v{{.RELEASE_VERSION}}"'
99+
- 'node -e "const { execFileSync } = require(''node:child_process''); const version = require(''./npm/package.json'').version; execFileSync(''git'', [''commit'', ''-m'', ''chore: release v'' + version], { stdio: ''inherit'' });"'
103100

104101
release:patch:
105102
desc: "Verify, bump the patch version, and create a release commit"

‎cmd/wfkit/main.go‎

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"os/exec"
1212
"os/signal"
13+
"path/filepath"
1314
"runtime"
1415
"strings"
1516
"syscall"
@@ -164,8 +165,9 @@ func interactiveMode(c *cli.Context) error {
164165
func initMode(c *cli.Context) error {
165166
name := c.String("name")
166167
packageManager := c.String("package-manager")
167-
types := c.Bool("types")
168168
initGit := c.Bool("init-git")
169+
skipInstall := c.Bool("skip-install")
170+
force := c.Bool("force")
169171

170172
// Попытка загрузить глобальный конфиг для подстановки значений по умолчанию
171173
importConfig, _ := globalconfig.LoadConfig()
@@ -182,6 +184,7 @@ func initMode(c *cli.Context) error {
182184

183185
// If the user didn't provide specific flags (or just ran 'wfkit init'), ask them interactively
184186
if c.NumFlags() == 0 {
187+
installDependencies := true
185188
err := huh.NewForm(
186189
huh.NewGroup(
187190
huh.NewInput().
@@ -205,8 +208,9 @@ func initMode(c *cli.Context) error {
205208
).
206209
Value(&packageManager),
207210
huh.NewConfirm().
208-
Title("Use TypeScript?").
209-
Value(&types),
211+
Title("Install dependencies now?").
212+
Description("Installs the generated project's local CLI and frontend tooling immediately.").
213+
Value(&installDependencies),
210214
huh.NewConfirm().
211215
Title("Initialize git repository?").
212216
Description("Runs `git init` in the new project directory.").
@@ -217,10 +221,12 @@ func initMode(c *cli.Context) error {
217221
if err != nil {
218222
return err
219223
}
224+
225+
skipInstall = !installDependencies
220226
}
221227

222228
if repositoryName == "" {
223-
repositoryName = name
229+
repositoryName = filepath.Base(filepath.Clean(name))
224230
}
225231

226232
// Сохраняем введенные данные в глобальный конфиг, чтобы использовать их в следующий раз
@@ -248,13 +254,16 @@ func initMode(c *cli.Context) error {
248254
}
249255

250256
opts := initconfig.Options{
251-
Name: name,
257+
ProjectDir: name,
258+
Name: filepath.Base(filepath.Clean(name)),
252259
PagesDir: pagesDir,
253260
GlobalEntry: globalEntry,
254261
GlobalVar: globalVar,
255-
Types: types,
256262
InitGit: initGit,
263+
Force: force,
264+
SkipInstall: skipInstall,
257265
PackageManager: packageManager,
266+
CLIValue: c.App.Version,
258267
GitHubUser: githubUser,
259268
RepositoryName: repositoryName,
260269
}
@@ -263,21 +272,49 @@ func initMode(c *cli.Context) error {
263272
return fmt.Errorf("initialization failed: %w", err)
264273
}
265274

275+
nextSteps := []string{fmt.Sprintf("cd %s", name)}
276+
if skipInstall {
277+
nextSteps = append(nextSteps, packageManagerInstallCommand(packageManager))
278+
}
279+
nextSteps = append(nextSteps, packageManagerScriptCommand(packageManager, "dev"), "wfkit doctor")
280+
266281
utils.PrintSuccessScreen(
267282
"Project initialized",
268283
"Your Webflow project scaffold is ready.",
269284
[]utils.SummaryMetric{
270-
{Label: "Project", Value: name, Tone: "success"},
285+
{Label: "Project", Value: opts.Name, Tone: "success"},
271286
{Label: "Package manager", Value: packageManager, Tone: "info"},
272-
{Label: "TypeScript", Value: map[bool]string{true: "on", false: "off"}[types], Tone: "info"},
287+
{Label: "Dependencies", Value: map[bool]string{true: "skipped", false: "installed"}[skipInstall], Tone: "info"},
273288
},
274-
fmt.Sprintf("cd %s", name),
275-
fmt.Sprintf("%s run start", packageManager),
276-
"wfkit doctor",
289+
nextSteps...,
277290
)
278291
return nil
279292
}
280293

294+
func packageManagerScriptCommand(packageManager, script string) string {
295+
switch packageManager {
296+
case "bun":
297+
return fmt.Sprintf("bun run %s", script)
298+
case "yarn", "pnpm":
299+
return fmt.Sprintf("%s %s", packageManager, script)
300+
default:
301+
return fmt.Sprintf("npm run %s", script)
302+
}
303+
}
304+
305+
func packageManagerInstallCommand(packageManager string) string {
306+
switch packageManager {
307+
case "bun":
308+
return "bun install"
309+
case "yarn":
310+
return "yarn"
311+
case "pnpm":
312+
return "pnpm install"
313+
default:
314+
return "npm install"
315+
}
316+
}
317+
281318
// updateMode checks for and processes updates
282319
func updateMode(c *cli.Context) error {
283320
updateManager := updater.NewUpdateManager(c.App.Version)
@@ -1033,8 +1070,9 @@ func main() {
10331070
&cli.StringFlag{Name: "pages-dir", Value: "src/pages", Usage: "Directory for pages"},
10341071
&cli.StringFlag{Name: "global-entry", Value: "src/global/index.ts", Usage: "Global entry file"},
10351072
&cli.StringFlag{Name: "global-var", Value: "WF", Usage: "Global variable name"},
1036-
&cli.BoolFlag{Name: "types", Value: true, Usage: "Generate TypeScript types"},
10371073
&cli.BoolFlag{Name: "init-git", Usage: "Initialize a local git repository inside the project"},
1074+
&cli.BoolFlag{Name: "skip-install", Usage: "Skip dependency installation after generating the scaffold"},
1075+
&cli.BoolFlag{Name: "force", Usage: "Allow writing scaffold files into an existing non-empty directory"},
10381076
&cli.StringFlag{Name: "package-manager", Value: "bun", Usage: "Package manager (npm, yarn, pnpm, bun)"},
10391077
},
10401078
Action: initMode,

‎internal/initialize/config/opts.go‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
// internal/initialize/config/opts.go
22
package config
33

4+
import "path/filepath"
5+
46
type Options struct {
7+
ProjectDir string
58
PagesDir string `default:"src/pages"`
69
GlobalEntry string `default:"src/global/index.ts"`
710
GlobalVar string `default:"WF"`
8-
Types bool
911
InitGit bool
12+
Force bool
13+
SkipInstall bool
1014
PackageManager string `default:"bun"`
15+
CLIValue string
1116
Name string
1217
GitHubUser string
1318
RepositoryName string
@@ -17,6 +22,12 @@ type Options struct {
1722

1823
// Метод для установки значений по умолчанию
1924
func (o *Options) SetDefaultValues() {
25+
if o.ProjectDir == "" {
26+
o.ProjectDir = o.Name
27+
}
28+
if o.Name == "" && o.ProjectDir != "" {
29+
o.Name = filepath.Base(filepath.Clean(o.ProjectDir))
30+
}
2031
if o.PagesDir == "" {
2132
o.PagesDir = "src/pages"
2233
}

‎internal/initialize/init.go‎

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func initProject(opts config.Options, installDependencies func(string) error, in
1919
// Установка значений по умолчанию
2020
opts.SetDefaultValues()
2121

22-
restoreDir, err := prepareProjectDir(opts.Name)
22+
restoreDir, err := prepareProjectDir(opts.ProjectDir, opts.Force)
2323
if err != nil {
2424
return err
2525
}
@@ -44,6 +44,11 @@ func initProject(opts config.Options, installDependencies func(string) error, in
4444
return err
4545
}
4646

47+
// Создание README проекта
48+
if err := steps.CreateProjectReadme(opts); err != nil {
49+
return err
50+
}
51+
4752
// Создание .gitignore
4853
if err := steps.CreateGitignore(); err != nil {
4954
return err
@@ -70,8 +75,10 @@ func initProject(opts config.Options, installDependencies func(string) error, in
7075
}
7176

7277
// Установка зависимостей
73-
if err := installDependencies(opts.PackageManager); err != nil {
74-
return err
78+
if !opts.SkipInstall {
79+
if err := installDependencies(opts.PackageManager); err != nil {
80+
return err
81+
}
7582
}
7683

7784
if opts.InitGit {
@@ -84,7 +91,7 @@ func initProject(opts config.Options, installDependencies func(string) error, in
8491
return nil
8592
}
8693

87-
func prepareProjectDir(name string) (func() error, error) {
94+
func prepareProjectDir(name string, force bool) (func() error, error) {
8895
projectDir := filepath.Clean(name)
8996
if projectDir == "" || projectDir == "." {
9097
return nil, fmt.Errorf("project name must not be empty")
@@ -95,6 +102,26 @@ func prepareProjectDir(name string) (func() error, error) {
95102
return nil, fmt.Errorf("failed to get current directory: %w", err)
96103
}
97104

105+
info, err := os.Stat(projectDir)
106+
switch {
107+
case err == nil && !info.IsDir():
108+
return nil, fmt.Errorf("project path %s already exists and is not a directory", projectDir)
109+
case err == nil:
110+
entries, readErr := os.ReadDir(projectDir)
111+
if readErr != nil {
112+
return nil, fmt.Errorf("failed to inspect project directory %s: %w", projectDir, readErr)
113+
}
114+
if len(entries) > 0 && !force {
115+
return nil, fmt.Errorf("project directory %s is not empty; rerun with --force to overwrite scaffold files", projectDir)
116+
}
117+
case os.IsNotExist(err):
118+
if err := os.MkdirAll(projectDir, 0755); err != nil {
119+
return nil, fmt.Errorf("failed to create project directory %s: %w", projectDir, err)
120+
}
121+
default:
122+
return nil, fmt.Errorf("failed to inspect project directory %s: %w", projectDir, err)
123+
}
124+
98125
if err := os.MkdirAll(projectDir, 0755); err != nil {
99126
return nil, fmt.Errorf("failed to create project directory %s: %w", projectDir, err)
100127
}

0 commit comments

Comments
 (0)