@upstatement/twix 0.1.3 → 0.1.5

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.
Files changed (4) hide show
  1. package/README.md +147 -10
  2. package/bin/twix.js +60 -0
  3. package/package.json +19 -11
  4. package/bin/twix +0 -23
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Twix
1
+ # Twix 🌱🍫
2
2
 
3
- A formatter for Twig templates with HTML.
3
+ An opinionated formatter for [Twig](https://twig.symfony.com/) templating engine. **Entirely coded with Claude.**
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,28 +8,165 @@ A formatter for Twig templates with HTML.
8
8
  npm install --save-dev @upstatement/twix
9
9
  ```
10
10
 
11
+ Or download the binary directly from [GitHub Releases](https://github.com/Upstatement/twix/releases).
12
+
11
13
  ## Usage
12
14
 
13
15
  ```bash
14
16
  # Format a file (in-place)
15
- npx twix path/to/file.twig
17
+ twix path/to/file.twig
16
18
 
17
19
  # Format multiple files
18
- npx twix "src/**/*.twig"
20
+ twix "src/**/*.twig"
19
21
 
20
22
  # Check if files are formatted (exit 1 if not)
21
- npx twix --check path/to/file.twig
23
+ twix --check path/to/file.twig
22
24
 
23
25
  # Format from stdin
24
- cat file.twig | npx twix --stdin
26
+ cat file.twig | twix --stdin
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ Create a `.twixrc.json` or `twix.config.json` file in your project root:
32
+
33
+ ```json
34
+ {
35
+ "indentSize": 2,
36
+ "indentStyle": "space",
37
+ "printWidth": 80,
38
+ "customTags": {
39
+ "block": ["if"],
40
+ "inline": ["set"],
41
+ "intermediate": ["else"]
42
+ }
43
+ }
25
44
  ```
26
45
 
46
+ ### Options
47
+
48
+ | Option | Type | Default | Description |
49
+ | ------------------------- | -------- | --------- | ------------------------------------------------------------------------- |
50
+ | `indentSize` | number | `2` | Number of spaces or tabs per indentation level |
51
+ | `indentStyle` | string | `"space"` | Use `"space"` or `"tab"` for indentation |
52
+ | `printWidth` | number | `80` | Maximum line width before wrapping attributes |
53
+ | `customTags.block` | string[] | `[]` | Custom Twig block tags (tags with bodies, e.g., `{% if %}...{% endif %}`) |
54
+ | `customTags.inline` | string[] | `[]` | Custom Twig inline tags (self-closing, e.g., `{% set = ... %}`) |
55
+ | `customTags.intermediate` | string[] | `[]` | Custom intermediate tags (like `{% else %}` or `{% case %}`) |
56
+
57
+ ### Built-in Tag Support
58
+
59
+ **Block tags** (have a body and `{% end* %}` tag):
60
+
61
+ - Standard Twig: `if`, `for`, `block`, `set`, `apply`, `autoescape`, `embed`, `filter`, `macro`, `sandbox`, `verbatim`, `with`
62
+ - Craft CMS: `switch`, `cache`, `js`, `css`, `nav`, `tag`
63
+
64
+ **Inline tags** (self-closing):
65
+
66
+ - Standard Twig: `extends`, `include`, `use`, `import`, `from`, `do`, `flush`, `deprecated`
67
+ - Craft CMS: `exit`, `header`, `paginate`, `redirect`, `requireLogin`, `requireAdmin`, `requireGuest`, `requirePermission`
68
+
69
+ **Intermediate tags** (appear within blocks):
70
+
71
+ - `else`, `elseif`, `case`, `default`
72
+
27
73
  ## Features
28
74
 
29
- - **AST-based formatting** - Accurate formatting that understands Twig and HTML structure
30
- - **Twig inside attributes** - Properly handles `{{ expressions }}` and `{% if %}` blocks in HTML attributes
31
- - **Whitespace control** - Preserves `{%- -%}` and `{{- -}}` modifiers
32
- - **Ignore blocks** - Use `{# twix:ignore-start #}` / `{# twix:ignore-end #}` to skip formatting
75
+ ### HTML Formatting
76
+
77
+ - Proper indentation of nested elements
78
+ - Block elements (`div`, `p`, `section`, etc.) get their own lines
79
+ - Inline elements (`span`, `a`, `strong`, etc.) stay on the same line when possible
80
+ - Void elements (`meta`, `link`, `input`, etc.) are self-closing
81
+ - Multi-line attribute formatting when attributes exceed `printWidth`
82
+
83
+ ### Twig Formatting
84
+
85
+ - Block tags are indented with their content
86
+ - Whitespace control modifiers (`{%- -%}`, `{{- -}}`) are preserved
87
+ - Comments (`{# #}`) are preserved exactly as-is (content inside comments is not formatted)
88
+
89
+ ### Expression Formatting
90
+
91
+ Twix parses and formats Twig expressions inside `{{ }}` and `{% %}` tags:
92
+
93
+ - Spaces around binary operators (`a + b`), after commas, inside hashes (`{ key: value }`)
94
+ - No spaces around pipes (`value|filter|another`)
95
+ - Strings normalized to double quotes (except when containing unescaped double quotes)
96
+ - Long method chains break with `.` at the start of each line
97
+ - Long function/method calls break arguments onto separate lines with trailing commas
98
+ - Hashes and arrays break onto multiple lines when they exceed `printWidth`
99
+ - Expressions inside string interpolation (`#{...}`) are formatted
100
+
101
+ ### Twig Inside HTML Attributes
102
+
103
+ Twix properly handles Twig embedded in HTML attribute values:
104
+
105
+ ```twig
106
+ <!-- Expressions in attributes -->
107
+ <div class="container {{ dynamicClass }}">
108
+
109
+ <!-- Conditional classes -->
110
+ <div class="item {% if active %}is-active{% endif %}">
111
+
112
+ <!-- Conditional attributes -->
113
+ <button {% if disabled %}disabled{% endif %}>
114
+ ```
115
+
116
+ ### Ignore Blocks
117
+
118
+ Skip formatting for specific sections using ignore directives:
119
+
120
+ ```twig
121
+ {# twix:ignore-start #}
122
+ <div class="preserve" data-value="exactly as-is" >
123
+ This content will not be formatted
124
+ </div>
125
+ {# twix:ignore-end #}
126
+ ```
127
+
128
+ ### Blank Line Handling
129
+
130
+ - Multiple consecutive blank lines are collapsed to a single blank line
131
+ - Blank lines immediately after opening block tags (`{% if %}`, `{% for %}`, etc.) are removed
132
+ - Intentional blank lines between statements are preserved
133
+
134
+ ## Limitations & Known Issues
135
+
136
+ ### Not Supported
137
+
138
+ - **No HTML entity handling** - Entities like `&nbsp;` are passed through unchanged
139
+ - **No CSS/JS formatting** - Content inside `<style>` and `<script>` tags is not formatted
140
+ - **Windows line endings** - Output uses Unix line endings (`\n`)
141
+ - **Complex Twig edge cases** - Some advanced Twig syntax may fall back to preserving original formatting
142
+
143
+ ## Editor Integration
144
+
145
+ ### Pre-commit Hook
146
+
147
+ Using [husky](https://typicode.github.io/husky/) and [lint-staged](https://github.com/okonet/lint-staged):
148
+
149
+ ```json
150
+ // package.json
151
+ {
152
+ "lint-staged": {
153
+ "*.twig": "twix"
154
+ }
155
+ }
156
+ ```
157
+
158
+ ## Development
159
+
160
+ ```bash
161
+ # Build
162
+ go build
163
+
164
+ # Run tests
165
+ go test ./...
166
+
167
+ # Build for all platforms
168
+ ./scripts/build.sh 0.1.0
169
+ ```
33
170
 
34
171
  ## License
35
172
 
package/bin/twix.js ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execFileSync } = require("child_process");
4
+ const path = require("path");
5
+ const os = require("os");
6
+
7
+ const platform = os.platform();
8
+ const arch = os.arch();
9
+
10
+ const PLATFORMS = {
11
+ "darwin-arm64": "@upstatement/twix-darwin-arm64",
12
+ "darwin-x64": "@upstatement/twix-darwin-x64",
13
+ "linux-arm64": "@upstatement/twix-linux-arm64",
14
+ "linux-x64": "@upstatement/twix-linux-x64",
15
+ };
16
+
17
+ const platformKey = `${platform}-${arch}`;
18
+ const packageName = PLATFORMS[platformKey];
19
+
20
+ if (!packageName) {
21
+ console.error(`Unsupported platform: ${platform}-${arch}`);
22
+ console.error(
23
+ `twix currently supports: darwin-arm64, darwin-x64, linux-arm64, linux-x64`,
24
+ );
25
+ process.exit(1);
26
+ }
27
+
28
+ let binaryPath;
29
+ try {
30
+ // Use require.resolve to find the binary in node_modules
31
+ // This works correctly with npm, pnpm, yarn, and monorepos
32
+ const packagePath = require.resolve(`${packageName}/package.json`);
33
+ binaryPath = path.join(path.dirname(packagePath), "bin", "twix");
34
+ } catch (e) {
35
+ console.error(
36
+ `Could not find the twix binary for your platform (${platformKey}).`,
37
+ );
38
+ console.error(`The package ${packageName} does not appear to be installed.`);
39
+ console.error("");
40
+ console.error("This can happen if:");
41
+ console.error(" 1. Your platform is not supported");
42
+ console.error(
43
+ " 2. Optional dependencies were not installed (try: npm install --include=optional)",
44
+ );
45
+ console.error(
46
+ " 3. You're using a package manager that doesn't install optional dependencies by default",
47
+ );
48
+ process.exit(1);
49
+ }
50
+
51
+ try {
52
+ execFileSync(binaryPath, process.argv.slice(2), { stdio: "inherit" });
53
+ } catch (e) {
54
+ if (e.status !== undefined) {
55
+ process.exit(e.status);
56
+ }
57
+ // If we get here, it's an actual error (not just a non-zero exit code)
58
+ console.error(`Failed to execute twix: ${e.message}`);
59
+ process.exit(1);
60
+ }
package/package.json CHANGED
@@ -1,17 +1,24 @@
1
1
  {
2
2
  "name": "@upstatement/twix",
3
- "version": "0.1.3",
4
- "description": "A formatter for Twig templates with HTML",
3
+ "version": "0.1.5",
4
+ "description": "An opinionated formatter for Twig templating engine",
5
+ "bin": {
6
+ "twix": "bin/twix.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "README.md"
11
+ ],
12
+ "optionalDependencies": {
13
+ "@upstatement/twix-darwin-arm64": "0.1.5",
14
+ "@upstatement/twix-darwin-x64": "0.1.5",
15
+ "@upstatement/twix-linux-arm64": "0.1.5",
16
+ "@upstatement/twix-linux-x64": "0.1.5"
17
+ },
5
18
  "repository": {
6
19
  "type": "git",
7
20
  "url": "git+https://github.com/Upstatement/twix.git"
8
21
  },
9
- "optionalDependencies": {
10
- "@upstatement/twix-darwin-arm64": "0.1.3",
11
- "@upstatement/twix-darwin-x64": "0.1.3",
12
- "@upstatement/twix-linux-arm64": "0.1.3",
13
- "@upstatement/twix-linux-x64": "0.1.3"
14
- },
15
22
  "keywords": [
16
23
  "twig",
17
24
  "formatter",
@@ -21,7 +28,8 @@
21
28
  ],
22
29
  "author": "Upstatement",
23
30
  "license": "MIT",
24
- "bin": {
25
- "twix": "bin/twix"
26
- }
31
+ "bugs": {
32
+ "url": "https://github.com/Upstatement/twix/issues"
33
+ },
34
+ "homepage": "https://github.com/Upstatement/twix#readme"
27
35
  }
package/bin/twix DELETED
@@ -1,23 +0,0 @@
1
- #!/bin/sh
2
- # Resolve the real path of this script (following symlinks)
3
- # This is needed because npm creates a symlink in node_modules/.bin/
4
- SCRIPT="$0"
5
- while [ -h "$SCRIPT" ]; do
6
- SCRIPTDIR="$(cd -P "$(dirname "$SCRIPT")" && pwd)"
7
- SCRIPT="$(readlink "$SCRIPT")"
8
- case "$SCRIPT" in
9
- /*) ;;
10
- *) SCRIPT="$SCRIPTDIR/$SCRIPT" ;;
11
- esac
12
- done
13
- SCRIPTDIR="$(cd -P "$(dirname "$SCRIPT")" && pwd)"
14
-
15
- # SCRIPTDIR is now the real bin/ directory inside @upstatement/twix
16
- # Go up two levels to @upstatement/, then into the platform package
17
- case "$(uname -s)-$(uname -m)" in
18
- Darwin-arm64) exec "$SCRIPTDIR/../../twix-darwin-arm64/bin/twix" "$@" ;;
19
- Darwin-x86_64) exec "$SCRIPTDIR/../../twix-darwin-x64/bin/twix" "$@" ;;
20
- Linux-aarch64) exec "$SCRIPTDIR/../../twix-linux-arm64/bin/twix" "$@" ;;
21
- Linux-x86_64) exec "$SCRIPTDIR/../../twix-linux-x64/bin/twix" "$@" ;;
22
- *) echo "Unsupported platform: $(uname -s)-$(uname -m)" >&2; exit 1 ;;
23
- esac