1+ #!/usr/bin/env node
2+
3+ const fs = require ( 'fs' ) ;
4+ const path = require ( 'path' ) ;
5+
6+ // Dynamic path resolution
7+ const scriptDir = __dirname ;
8+ const ruleParserPath = path . join ( scriptDir , 'rule-parser' ) ;
9+ const styleRulesPath = path . join ( scriptDir , 'style-rules.yml' ) ;
10+
11+ /**
12+ * Flexible content tester for Strapi 12 Rules
13+ * Usage:
14+ * node test-content.js --file path/to/file.md
15+ * node test-content.js --text "Some text to test"
16+ * node test-content.js --stdin (then paste content)
17+ */
18+ class FlexibleContentTester {
19+ constructor ( ) {
20+ try {
21+ const Strapi12RulesParser = require ( ruleParserPath ) ;
22+ this . ruleParser = new Strapi12RulesParser ( styleRulesPath ) ;
23+ console . log ( '✅ Strapi 12 Rules loaded successfully' ) ;
24+ } catch ( error ) {
25+ console . error ( '❌ Failed to load rules:' , error . message ) ;
26+ process . exit ( 1 ) ;
27+ }
28+ }
29+
30+ async testFile ( filePath ) {
31+ console . log ( '🎯 File Validation Test' ) ;
32+ console . log ( '======================' ) ;
33+ console . log ( `📄 File: ${ filePath } ` ) ;
34+ console . log ( '' ) ;
35+
36+ if ( ! fs . existsSync ( filePath ) ) {
37+ console . error ( `❌ File not found: ${ filePath } ` ) ;
38+ return ;
39+ }
40+
41+ const content = fs . readFileSync ( filePath , 'utf8' ) ;
42+ await this . validateContent ( content , filePath ) ;
43+ }
44+
45+ async testText ( text ) {
46+ console . log ( '🎯 Text Validation Test' ) ;
47+ console . log ( '======================' ) ;
48+ console . log ( `📝 Content length: ${ text . length } characters` ) ;
49+ console . log ( `📄 Lines: ${ text . split ( '\n' ) . length } ` ) ;
50+ console . log ( '' ) ;
51+
52+ await this . validateContent ( text , '<text-input>' ) ;
53+ }
54+
55+ async testStdin ( ) {
56+ console . log ( '🎯 Interactive Text Validation' ) ;
57+ console . log ( '==============================' ) ;
58+ console . log ( '📝 Paste your content below (Ctrl+D when finished):' ) ;
59+ console . log ( '' ) ;
60+
61+ let content = '' ;
62+ process . stdin . setEncoding ( 'utf8' ) ;
63+
64+ for await ( const chunk of process . stdin ) {
65+ content += chunk ;
66+ }
67+
68+ if ( content . trim ( ) === '' ) {
69+ console . log ( '❌ No content provided' ) ;
70+ return ;
71+ }
72+
73+ console . log ( `✅ Content received (${ content . length } characters)` ) ;
74+ console . log ( '' ) ;
75+ await this . validateContent ( content , '<stdin>' ) ;
76+ }
77+
78+ async validateContent ( content , source ) {
79+ try {
80+ const allRules = this . ruleParser . getAllRules ( ) ;
81+ console . log ( `🔍 Applying ${ allRules . length } validation rules...` ) ;
82+ console . log ( '' ) ;
83+
84+ let totalIssues = 0 ;
85+ const issuesByRule = { } ;
86+ const issuesBySeverity = { error : [ ] , warning : [ ] , suggestion : [ ] } ;
87+
88+ // Apply all rules
89+ allRules . forEach ( rule => {
90+ try {
91+ const issues = rule . validator ( content , source ) ;
92+
93+ if ( issues . length > 0 ) {
94+ issuesByRule [ rule . id ] = {
95+ rule : rule ,
96+ issues : issues
97+ } ;
98+
99+ issues . forEach ( issue => {
100+ issue . ruleId = rule . id ;
101+ issue . ruleDescription = rule . description ;
102+ totalIssues ++ ;
103+
104+ if ( issuesBySeverity [ issue . severity ] ) {
105+ issuesBySeverity [ issue . severity ] . push ( issue ) ;
106+ } else {
107+ issuesBySeverity . warning . push ( issue ) ;
108+ }
109+ } ) ;
110+ }
111+ } catch ( error ) {
112+ console . log ( `⚠️ Rule ${ rule . id } failed: ${ error . message } ` ) ;
113+ }
114+ } ) ;
115+
116+ // Display results
117+ this . displayResults ( totalIssues , issuesBySeverity , issuesByRule , source ) ;
118+
119+ // Save results
120+ const results = {
121+ timestamp : new Date ( ) . toISOString ( ) ,
122+ source : source ,
123+ contentLength : content . length ,
124+ totalIssues : totalIssues ,
125+ issuesBySeverity : {
126+ errors : issuesBySeverity . error . length ,
127+ warnings : issuesBySeverity . warning . length ,
128+ suggestions : issuesBySeverity . suggestion . length
129+ } ,
130+ issues : issuesBySeverity
131+ } ;
132+
133+ fs . writeFileSync ( 'content-validation-results.json' , JSON . stringify ( results , null , 2 ) ) ;
134+ console . log ( '💾 Detailed results saved to: content-validation-results.json' ) ;
135+
136+ } catch ( error ) {
137+ console . error ( '❌ Validation failed:' , error . message ) ;
138+ }
139+ }
140+
141+ displayResults ( totalIssues , issuesBySeverity , issuesByRule , source ) {
142+ console . log ( '📊 Validation Results' ) ;
143+ console . log ( '====================' ) ;
144+ console . log ( `Total issues found: ${ totalIssues } ` ) ;
145+ console . log ( `🚨 Critical errors: ${ issuesBySeverity . error . length } ` ) ;
146+ console . log ( `⚠️ Warnings: ${ issuesBySeverity . warning . length } ` ) ;
147+ console . log ( `💡 Suggestions: ${ issuesBySeverity . suggestion . length } ` ) ;
148+ console . log ( '' ) ;
149+
150+ if ( totalIssues === 0 ) {
151+ console . log ( '🎉 Perfect! Your content follows all 12 Strapi rules!' ) ;
152+ console . log ( '' ) ;
153+ return ;
154+ }
155+
156+ // Show critical errors first
157+ if ( issuesBySeverity . error . length > 0 ) {
158+ console . log ( '🚨 CRITICAL ERRORS (must fix):' ) ;
159+ console . log ( '================================' ) ;
160+ issuesBySeverity . error . forEach ( issue => {
161+ console . log ( `📍 Line ${ issue . line } : ${ issue . message } ` ) ;
162+ if ( issue . suggestion ) {
163+ console . log ( ` 💡 ${ issue . suggestion } ` ) ;
164+ }
165+ console . log ( ` 📚 Rule: ${ issue . ruleId } ` ) ;
166+ console . log ( '' ) ;
167+ } ) ;
168+ }
169+
170+ // Show warnings
171+ if ( issuesBySeverity . warning . length > 0 ) {
172+ console . log ( '⚠️ WARNINGS (should address):' ) ;
173+ console . log ( '===============================' ) ;
174+ issuesBySeverity . warning . forEach ( issue => {
175+ console . log ( `📍 Line ${ issue . line } : ${ issue . message } ` ) ;
176+ if ( issue . suggestion ) {
177+ console . log ( ` 💡 ${ issue . suggestion } ` ) ;
178+ }
179+ console . log ( ` 📚 Rule: ${ issue . ruleId } ` ) ;
180+ console . log ( '' ) ;
181+ } ) ;
182+ }
183+
184+ // Show suggestions
185+ if ( issuesBySeverity . suggestion . length > 0 ) {
186+ console . log ( '💡 SUGGESTIONS (nice to have):' ) ;
187+ console . log ( '===============================' ) ;
188+ issuesBySeverity . suggestion . forEach ( issue => {
189+ console . log ( `📍 Line ${ issue . line } : ${ issue . message } ` ) ;
190+ if ( issue . suggestion ) {
191+ console . log ( ` 💡 ${ issue . suggestion } ` ) ;
192+ }
193+ console . log ( ` 📚 Rule: ${ issue . ruleId } ` ) ;
194+ console . log ( '' ) ;
195+ } ) ;
196+ }
197+
198+ // Summary by rule
199+ console . log ( '📋 Issues by Rule:' ) ;
200+ console . log ( '==================' ) ;
201+ Object . entries ( issuesByRule ) . forEach ( ( [ ruleId , data ] ) => {
202+ console . log ( `${ data . rule . category === 'critical' ? '🚨' : '⚠️' } ${ ruleId } : ${ data . issues . length } issue(s)` ) ;
203+ } ) ;
204+ console . log ( '' ) ;
205+
206+ // GitHub-style comment
207+ console . log ( '📝 GitHub Comment Preview:' ) ;
208+ console . log ( '===========================' ) ;
209+ const comment = this . generateGitHubComment ( totalIssues , issuesBySeverity ) ;
210+ console . log ( comment ) ;
211+ }
212+
213+ generateGitHubComment ( totalIssues , issuesBySeverity ) {
214+ let comment = `## 🎯 Strapi Documentation Style Review (LOCAL TEST)\n\n` ;
215+ comment += `*Automated analysis using Strapi's 12 Rules of Technical Writing*\n\n` ;
216+
217+ comment += '### 📊 Analysis Results\n' ;
218+ comment += `- **Total issues:** ${ totalIssues } \n` ;
219+ comment += `- **Critical errors:** ${ issuesBySeverity . error . length } 🚨\n` ;
220+ comment += `- **Warnings:** ${ issuesBySeverity . warning . length } ⚠️\n` ;
221+ comment += `- **Suggestions:** ${ issuesBySeverity . suggestion . length } 💡\n\n` ;
222+
223+ if ( totalIssues === 0 ) {
224+ comment += '🎉 **Perfect!** Your content follows all 12 technical writing rules.\n\n' ;
225+ } else {
226+ comment += `⚠️ Found ${ totalIssues } issues that should be addressed.\n\n` ;
227+
228+ // Show top 3 issues
229+ const allIssues = [ ...issuesBySeverity . error , ...issuesBySeverity . warning , ...issuesBySeverity . suggestion ] ;
230+ if ( allIssues . length > 0 ) {
231+ comment += '**Top Issues:**\n' ;
232+ allIssues . slice ( 0 , 3 ) . forEach ( issue => {
233+ comment += `- Line ${ issue . line } : ${ issue . message } \n` ;
234+ } ) ;
235+ comment += '\n' ;
236+ }
237+ }
238+
239+ comment += '**📚 Resources:**\n' ;
240+ comment += '- [Strapi\'s 12 Rules of Technical Writing](https://strapi.notion.site/12-Rules-of-Technical-Writing-c75e080e6b19432287b3dd61c2c9fa04)\n' ;
241+ comment += '- [Documentation Style Guide](https://github.com/strapi/documentation/blob/main/STYLE_GUIDE.pdf)\n\n' ;
242+ comment += '*🧪 This is a local test simulation.*\n' ;
243+
244+ return comment ;
245+ }
246+ }
247+
248+ // Command line interface
249+ async function main ( ) {
250+ const args = process . argv . slice ( 2 ) ;
251+ const tester = new FlexibleContentTester ( ) ;
252+
253+ if ( args . length === 0 || args . includes ( '--help' ) || args . includes ( '-h' ) ) {
254+ console . log ( '🎯 Strapi Content Validation Tester' ) ;
255+ console . log ( '====================================' ) ;
256+ console . log ( '' ) ;
257+ console . log ( 'Usage:' ) ;
258+ console . log ( ' node test-content.js --file <path> Test a specific file' ) ;
259+ console . log ( ' node test-content.js --text "<text>" Test provided text' ) ;
260+ console . log ( ' node test-content.js --stdin Test content from stdin' ) ;
261+ console . log ( '' ) ;
262+ console . log ( 'Examples:' ) ;
263+ console . log ( ' node test-content.js --file docs/api/content-types.md' ) ;
264+ console . log ( ' node test-content.js --text "This is easy to understand"' ) ;
265+ console . log ( ' echo "Some content" | node test-content.js --stdin' ) ;
266+ console . log ( ' node test-content.js --stdin # then paste content manually' ) ;
267+ console . log ( '' ) ;
268+ return ;
269+ }
270+
271+ if ( args . includes ( '--file' ) || args . includes ( '-f' ) ) {
272+ const fileIndex = args . findIndex ( arg => arg === '--file' || arg === '-f' ) ;
273+ const filePath = args [ fileIndex + 1 ] ;
274+
275+ if ( ! filePath ) {
276+ console . error ( '❌ Please provide a file path after --file' ) ;
277+ return ;
278+ }
279+
280+ await tester . testFile ( filePath ) ;
281+
282+ } else if ( args . includes ( '--text' ) || args . includes ( '-t' ) ) {
283+ const textIndex = args . findIndex ( arg => arg === '--text' || arg === '-t' ) ;
284+ const text = args [ textIndex + 1 ] ;
285+
286+ if ( ! text ) {
287+ console . error ( '❌ Please provide text after --text' ) ;
288+ return ;
289+ }
290+
291+ await tester . testText ( text ) ;
292+
293+ } else if ( args . includes ( '--stdin' ) || args . includes ( '-s' ) ) {
294+ await tester . testStdin ( ) ;
295+
296+ } else {
297+ console . error ( '❌ Unknown option. Use --help for usage information.' ) ;
298+ }
299+ }
300+
301+ if ( require . main === module ) {
302+ main ( ) . catch ( console . error ) ;
303+ }
304+
305+ module . exports = FlexibleContentTester ;
0 commit comments