quackage 1.0.56 → 1.0.57

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quackage",
3
- "version": "1.0.56",
3
+ "version": "1.0.57",
4
4
  "description": "Building. Testing. Quacking. Reloading.",
5
5
  "main": "source/Quackage-CLIProgram.js",
6
6
  "scripts": {
@@ -20,6 +20,7 @@ let _Pict = new libCLIProgram(
20
20
  require('./commands/Quackage-Command-UpdatePackage.js'),
21
21
  require('./commands/Quackage-Command-UpdatePackage-Luxury.js'),
22
22
  require('./commands/Quackage-Command-Lint.js'),
23
+ require('./commands/Quackage-Command-UpdateNodeGitignore.js'),
23
24
 
24
25
  // Mocha test execution
25
26
  require('./commands/Quackage-Command-RunMochaTests.js'),
@@ -0,0 +1,209 @@
1
+ const libCommandLineCommand = require('pict-service-commandlineutility').ServiceCommandLineCommand;
2
+ const libFS = require('fs');
3
+ const libPath = require('path');
4
+
5
+ class QuackageCommandUpdateNodeGitignore extends libCommandLineCommand
6
+ {
7
+ constructor(pFable, pManifest, pServiceHash)
8
+ {
9
+ super(pFable, pManifest, pServiceHash);
10
+
11
+ this.options.CommandKeyword = 'update-node-gitignore';
12
+ this.options.Description = 'Check the .gitignore in the current folder against a prototype node module .gitignore and add any missing entries';
13
+ this.options.Aliases.push('update-gitignore');
14
+ this.options.Aliases.push('gitignore');
15
+
16
+ this.options.CommandOptions.push({ Name: '-d, --dry-run', Description: 'Show what would be added without modifying the file' });
17
+
18
+ this.addCommand();
19
+ }
20
+
21
+ // Parse a .gitignore file into an array of structured blocks.
22
+ // Each block is { comment: string|null, entries: string[] }
23
+ // representing a comment header and the entries that follow it.
24
+ parseGitignore(pContent)
25
+ {
26
+ let tmpLines = pContent.split('\n');
27
+ let tmpBlocks = [];
28
+ let tmpCurrentBlock = { comment: null, entries: [] };
29
+
30
+ for (let i = 0; i < tmpLines.length; i++)
31
+ {
32
+ let tmpLine = tmpLines[i];
33
+ let tmpTrimmed = tmpLine.trim();
34
+
35
+ if (tmpTrimmed.startsWith('#'))
36
+ {
37
+ // If we have accumulated entries, push the current block
38
+ if (tmpCurrentBlock.comment !== null || tmpCurrentBlock.entries.length > 0)
39
+ {
40
+ tmpBlocks.push(tmpCurrentBlock);
41
+ }
42
+ tmpCurrentBlock = { comment: tmpLine, entries: [] };
43
+ }
44
+ else if (tmpTrimmed.length > 0)
45
+ {
46
+ tmpCurrentBlock.entries.push(tmpLine);
47
+ }
48
+ // blank lines are structural separators -- if we have a pending block with entries, close it
49
+ else if (tmpCurrentBlock.entries.length > 0)
50
+ {
51
+ tmpBlocks.push(tmpCurrentBlock);
52
+ tmpCurrentBlock = { comment: null, entries: [] };
53
+ }
54
+ }
55
+
56
+ // Push any remaining block
57
+ if (tmpCurrentBlock.comment !== null || tmpCurrentBlock.entries.length > 0)
58
+ {
59
+ tmpBlocks.push(tmpCurrentBlock);
60
+ }
61
+
62
+ return tmpBlocks;
63
+ }
64
+
65
+ // Collect all non-comment, non-blank entries from a gitignore into a Set
66
+ collectEntries(pContent)
67
+ {
68
+ let tmpEntries = new Set();
69
+ let tmpLines = pContent.split('\n');
70
+ for (let i = 0; i < tmpLines.length; i++)
71
+ {
72
+ let tmpTrimmed = tmpLines[i].trim();
73
+ if (tmpTrimmed.length > 0 && !tmpTrimmed.startsWith('#'))
74
+ {
75
+ tmpEntries.add(tmpTrimmed);
76
+ }
77
+ }
78
+ return tmpEntries;
79
+ }
80
+
81
+ onRunAsync(fCallback)
82
+ {
83
+ let tmpOptions = this.CommandOptions;
84
+ let tmpCWD = this.fable.AppData.CWD;
85
+ let tmpProjectGitignorePath = `${tmpCWD}/.gitignore`;
86
+
87
+ // Load the prototype gitignore from quackage's own .gitignore
88
+ let tmpPrototypePath = libPath.join(this.fable.AppData.QuackageFolder, '.gitignore');
89
+
90
+ if (!libFS.existsSync(tmpPrototypePath))
91
+ {
92
+ this.log.error(`Prototype .gitignore not found at [${tmpPrototypePath}]`);
93
+ return fCallback(new Error('Prototype .gitignore not found'));
94
+ }
95
+
96
+ let tmpPrototypeContent = libFS.readFileSync(tmpPrototypePath, 'utf8');
97
+
98
+ // Load or create the project gitignore
99
+ let tmpProjectContent = '';
100
+ let tmpProjectExists = libFS.existsSync(tmpProjectGitignorePath);
101
+ if (tmpProjectExists)
102
+ {
103
+ tmpProjectContent = libFS.readFileSync(tmpProjectGitignorePath, 'utf8');
104
+ }
105
+
106
+ this.log.info(`Checking .gitignore in [${tmpCWD}] against prototype...`);
107
+
108
+ // Collect all existing entries from the project gitignore
109
+ let tmpExistingEntries = this.collectEntries(tmpProjectContent);
110
+
111
+ // Parse the prototype into blocks so we can add missing entries with their section context
112
+ let tmpPrototypeBlocks = this.parseGitignore(tmpPrototypeContent);
113
+
114
+ // Walk each prototype block and find entries missing from the project
115
+ let tmpMissingBlocks = [];
116
+ let tmpTotalMissing = 0;
117
+
118
+ for (let i = 0; i < tmpPrototypeBlocks.length; i++)
119
+ {
120
+ let tmpBlock = tmpPrototypeBlocks[i];
121
+ let tmpMissingEntries = [];
122
+
123
+ for (let j = 0; j < tmpBlock.entries.length; j++)
124
+ {
125
+ let tmpEntry = tmpBlock.entries[j].trim();
126
+ if (!tmpExistingEntries.has(tmpEntry))
127
+ {
128
+ tmpMissingEntries.push(tmpBlock.entries[j]);
129
+ }
130
+ }
131
+
132
+ if (tmpMissingEntries.length > 0)
133
+ {
134
+ tmpMissingBlocks.push({ comment: tmpBlock.comment, entries: tmpMissingEntries });
135
+ tmpTotalMissing += tmpMissingEntries.length;
136
+ }
137
+ }
138
+
139
+ if (tmpTotalMissing === 0)
140
+ {
141
+ this.log.info(` --> .gitignore is up to date; nothing to add.`);
142
+ return fCallback();
143
+ }
144
+
145
+ // Report what will be added
146
+ this.log.info(` --> Found ${tmpTotalMissing} missing ${tmpTotalMissing === 1 ? 'entry' : 'entries'}:`);
147
+ for (let i = 0; i < tmpMissingBlocks.length; i++)
148
+ {
149
+ let tmpBlock = tmpMissingBlocks[i];
150
+ if (tmpBlock.comment)
151
+ {
152
+ this.log.info(` ${tmpBlock.comment}`);
153
+ }
154
+ for (let j = 0; j < tmpBlock.entries.length; j++)
155
+ {
156
+ this.log.info(` + ${tmpBlock.entries[j]}`);
157
+ }
158
+ }
159
+
160
+ if (tmpOptions.dry_run || tmpOptions.dryRun)
161
+ {
162
+ this.log.info(` --> Dry run; no changes written.`);
163
+ return fCallback();
164
+ }
165
+
166
+ // Build the section to append
167
+ let tmpAppendLines = [];
168
+ tmpAppendLines.push('');
169
+ tmpAppendLines.push('# Added by quackage update-node-gitignore');
170
+
171
+ for (let i = 0; i < tmpMissingBlocks.length; i++)
172
+ {
173
+ let tmpBlock = tmpMissingBlocks[i];
174
+ tmpAppendLines.push('');
175
+ if (tmpBlock.comment)
176
+ {
177
+ tmpAppendLines.push(tmpBlock.comment);
178
+ }
179
+ for (let j = 0; j < tmpBlock.entries.length; j++)
180
+ {
181
+ tmpAppendLines.push(tmpBlock.entries[j]);
182
+ }
183
+ }
184
+
185
+ let tmpAppendContent = tmpAppendLines.join('\n') + '\n';
186
+
187
+ // Ensure the existing file ends with a newline before appending
188
+ if (tmpProjectContent.length > 0 && !tmpProjectContent.endsWith('\n'))
189
+ {
190
+ tmpAppendContent = '\n' + tmpAppendContent;
191
+ }
192
+
193
+ let tmpNewContent = tmpProjectContent + tmpAppendContent;
194
+
195
+ if (tmpProjectExists)
196
+ {
197
+ this.log.info(` --> Backing up .gitignore to .gitignore.quackage.bak ...`);
198
+ libFS.writeFileSync(`${tmpCWD}/.gitignore.quackage.bak`, tmpProjectContent);
199
+ }
200
+
201
+ this.log.info(` --> Writing updated .gitignore ...`);
202
+ libFS.writeFileSync(tmpProjectGitignorePath, tmpNewContent);
203
+ this.log.info(` --> Done; ${tmpTotalMissing} ${tmpTotalMissing === 1 ? 'entry' : 'entries'} added.`);
204
+
205
+ return fCallback();
206
+ };
207
+ }
208
+
209
+ module.exports = QuackageCommandUpdateNodeGitignore;