-
Notifications
You must be signed in to change notification settings - Fork 1
fix(templates): remove obsolete Vite React configuration #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # Generated Project Formatting Implementation Plan | ||
|
|
||
| > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. | ||
|
|
||
| **Goal:** Freshly generated projects pass formatting checks, and failed commit checks show a repair command that works from the workspace root. | ||
|
|
||
| **Architecture:** Format the bundled Node templates with their declared Oxfmt version. Update generated package manifests by replacing only the requested JSON fields, preserving unrelated layout, key order, and literal characters. Append a localized repair hint at the One hk command boundary without changing child output or exit status. | ||
|
|
||
| **Tech Stack:** Go, encoding/json, Cobra, Oxfmt, hk, mise. | ||
|
|
||
| --- | ||
|
|
||
| ### Task 1: Preserve generated package formatting | ||
|
|
||
| **Files:** `packages/cli/internal/modules/creation/languages.go`, `ordered_json.go`, a focused JSON field editor, and creation tests. | ||
|
|
||
| 1. Add failing tests for package name/manager updates and script replacement that preserve tabs, arrays, field order, and shell characters. | ||
| 2. Replace whole-object map serialization with JSON-decoder-based field edits. Encode changed strings without HTML escaping; preserve script ordering. | ||
| 3. Keep root workspace package generation formatted and preserve existing root fields during workspace updates. | ||
| 4. Run creation tests for pnpm, npm, yarn, and bun. | ||
|
|
||
| ### Task 2: Normalize templates and verify generated projects | ||
|
|
||
| **Files:** `packages/templates/` Node templates and a creation integration test. | ||
|
|
||
| 1. Run Oxfmt 0.67.0 within each Node template, using its own EditorConfig; inspect formatting-only changes. | ||
| 2. Synchronize bundled resources with `task sync-bundled`. | ||
| 3. Generate each Node project through the creation service, then run the real formatter against the generated files using `ONE_TEST_OXFMT_BINARY`. | ||
| 4. Validate React's generated lint and formatting checks and a real first Git commit in a temporary workspace. | ||
|
|
||
| ### Task 3: Actionable hook failures | ||
|
|
||
| **Files:** `packages/cli/internal/transport/cobra/hooks/cmd.go`, its tests, English/Chinese i18n catalogs, and hook documentation. | ||
|
|
||
| 1. Add tests for failed pre-commit/check commands, successful checks, commit-msg failures, and cancellation. | ||
| 2. After failed checks, print a localized hint to run `one hk fix` from the workspace root, review and stage changes, then retry. Do not suggest formatting fixes for commit-message errors or interrupted commands. | ||
| 3. Document that `one mise` keeps the current directory and show a complete subproject command. | ||
| 4. Run `task check` and inspect the final diff. Leave changes for review without automatically committing or pushing. | ||
|
|
||
| ## Validation results | ||
|
|
||
| - All eight bundled Node templates were formatted with Oxfmt 0.67.0; only the Expo home screen required a source formatting change. | ||
| - The creation-service integration test generated all eight Node projects and verified both their files and workspace package manifests with the real formatter. | ||
| - Package edit regressions cover pnpm/npm/yarn/bun, indentation, compact arrays, readable shell operators, field removal/addition, escaped keys, CRLF, and idempotence. | ||
| - A temporary React workspace created by the compiled CLI installed dependencies and completed its first Git commit with the real hk lint, formatting, and commit-message checks. | ||
| - A deliberately misformatted package manifest blocked the next temporary commit and displayed the repair hint. Running `one hk fix` at the workspace root restored formatting without changing the index. | ||
| - `task check` passed, including Go/E2E tests and all 56 Dashboard tests. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| package creation | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/json" | ||
| "fmt" | ||
| "sort" | ||
| ) | ||
|
|
||
| // marshalJSONValue retains shell operators and version ranges as readable JSON | ||
| // strings instead of escaping &, < and > for embedding in HTML. | ||
| func marshalJSONValue(value any) ([]byte, error) { | ||
| var b bytes.Buffer | ||
| encoder := json.NewEncoder(&b) | ||
| encoder.SetEscapeHTML(false) | ||
| if err := encoder.Encode(value); err != nil { | ||
| return nil, err | ||
| } | ||
| return bytes.TrimSuffix(b.Bytes(), []byte("\n")), nil | ||
| } | ||
|
|
||
| // updateJSONFields edits immediate object members, leaving all other bytes | ||
| // intact. A nil value removes a field; json.RawMessage("null") sets JSON null. | ||
| // This preserves a template's indentation, ordering and compact arrays without | ||
| // requiring Node or a formatter to be installed during project creation. | ||
| func updateJSONFields(raw []byte, updates map[string]json.RawMessage) ([]byte, error) { | ||
| keys := make([]string, 0, len(updates)) | ||
| for key := range updates { | ||
| keys = append(keys, key) | ||
| } | ||
| sort.Strings(keys) | ||
| for _, key := range keys { | ||
| var err error | ||
| raw, err = updateJSONField(raw, key, updates[key]) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
| return raw, nil | ||
| } | ||
|
|
||
| type jsonField struct { | ||
| key string | ||
| keyStart, valueStart, end int | ||
| } | ||
|
|
||
| func updateJSONField(raw []byte, key string, value json.RawMessage) ([]byte, error) { | ||
| if !json.Valid(raw) || value != nil && !json.Valid(value) { | ||
| return nil, fmt.Errorf("invalid JSON while updating %q", key) | ||
| } | ||
| decoder := json.NewDecoder(bytes.NewReader(raw)) | ||
| token, _ := decoder.Token() | ||
| if token != json.Delim('{') { | ||
| return nil, fmt.Errorf("JSON document must be an object") | ||
| } | ||
| openEnd := int(decoder.InputOffset()) | ||
| var fields []jsonField | ||
| seen := make(map[string]bool) | ||
| for decoder.More() { | ||
| start := int(decoder.InputOffset()) | ||
| for raw[start] == ',' || raw[start] == ' ' || raw[start] == '\t' || raw[start] == '\r' || raw[start] == '\n' { | ||
| start++ | ||
| } | ||
| token, err := decoder.Token() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| fieldKey := token.(string) | ||
| if seen[fieldKey] { | ||
| return nil, fmt.Errorf("duplicate JSON field %q", fieldKey) | ||
| } | ||
| seen[fieldKey] = true | ||
| var fieldValue json.RawMessage | ||
| if err := decoder.Decode(&fieldValue); err != nil { | ||
| return nil, err | ||
| } | ||
| end := int(decoder.InputOffset()) | ||
| fields = append(fields, jsonField{fieldKey, start, end - len(fieldValue), end}) | ||
| } | ||
| _, _ = decoder.Token() | ||
| closeStart := int(decoder.InputOffset()) - 1 | ||
| for i, field := range fields { | ||
| if field.key != key { | ||
| continue | ||
| } | ||
| if value != nil { | ||
| return replaceJSONBytes(raw, field.valueStart, field.end, value), nil | ||
| } | ||
| if len(fields) == 1 { | ||
| return replaceJSONBytes(raw, openEnd, closeStart, nil), nil | ||
| } | ||
| if i == 0 { | ||
| return replaceJSONBytes(raw, field.keyStart, fields[1].keyStart, nil), nil | ||
| } | ||
| return replaceJSONBytes(raw, fields[i-1].end, field.end, nil), nil | ||
| } | ||
| if value == nil { | ||
| return raw, nil | ||
| } | ||
| encodedKey, _ := json.Marshal(key) | ||
| member := append(append(encodedKey, ':', ' '), value...) | ||
| if len(fields) == 0 { | ||
| return replaceJSONBytes(raw, openEnd, closeStart, member), nil | ||
| } | ||
| // Reuse the first member's leading whitespace, including CRLF or tabs. | ||
| prefix := raw[openEnd:fields[0].keyStart] | ||
| addition := append([]byte{','}, prefix...) | ||
| addition = append(addition, member...) | ||
| end := fields[len(fields)-1].end | ||
| return replaceJSONBytes(raw, end, end, addition), nil | ||
| } | ||
|
|
||
| func replaceJSONBytes(raw []byte, start, end int, replacement []byte) []byte { | ||
| after := make([]byte, 0, len(raw)+len(replacement)-(end-start)) | ||
| after = append(after, raw[:start]...) | ||
| after = append(after, replacement...) | ||
| return append(after, raw[end:]...) | ||
| } |
41 changes: 41 additions & 0 deletions
41
packages/cli/internal/modules/creation/json_fields_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package creation | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestJSONFieldEditsPreserveSurroundingContent(t *testing.T) { | ||
| for _, tc := range []struct { | ||
| name, before, key, value, after string | ||
| }{ | ||
| {"remove first", "{\n \"manager\": \"pnpm\",\n \"name\": \"demo\"\n}\n", "manager", "", "{\n \"name\": \"demo\"\n}\n"}, | ||
| {"remove last", "{\n\t\"name\": \"demo\",\n\t\"manager\": \"pnpm\"\n}\n", "manager", "", "{\n\t\"name\": \"demo\"\n}\n"}, | ||
| {"remove middle", `{"name":"demo","manager":"pnpm","files":["src","dist"]}`, "manager", "", `{"name":"demo","files":["src","dist"]}`}, | ||
| {"remove only", "{\n \"manager\": \"pnpm\"\n}\n", "manager", "", "{}\n"}, | ||
| {"absent removal", `{"name":"demo"}`, "manager", "", `{"name":"demo"}`}, | ||
| {"empty object", "{}\n", "private", "true", "{\"private\": true}\n"}, | ||
| {"append with tabs and CRLF", "{\r\n\t\"name\": \"demo\"\r\n}\r\n", "private", "true", "{\r\n\t\"name\": \"demo\",\r\n\t\"private\": true\r\n}\r\n"}, | ||
| {"escaped key", `{"na\u006de" : "old", "exports":{".":["a","b"]}}`, "name", `"new"`, `{"na\u006de" : "new", "exports":{".":["a","b"]}}`}, | ||
| {"null value", `{"name":"demo","custom":false}`, "custom", "null", `{"name":"demo","custom":null}`}, | ||
| } { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| var value json.RawMessage | ||
| if tc.value != "" { | ||
| value = json.RawMessage(tc.value) | ||
| } | ||
| got, err := updateJSONFields([]byte(tc.before), map[string]json.RawMessage{tc.key: value}) | ||
| if err != nil || string(got) != tc.after || !json.Valid(got) { | ||
| t.Fatalf("edit: %v\nwant %q\ngot %q", err, tc.after, got) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestJSONFieldEditsRejectInvalidOrAmbiguousObjects(t *testing.T) { | ||
| for _, source := range []string{`null`, `[]`, `{"name":`, `{"name":"a","name":"b"}`, `{} {}`} { | ||
| if _, err := updateJSONFields([]byte(source), map[string]json.RawMessage{"name": json.RawMessage(`"web"`)}); err == nil { | ||
| t.Errorf("accepted %q", source) | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.