spec-up-t 1.6.15 → 1.6.17

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": "spec-up-t",
3
- "version": "1.6.15",
3
+ "version": "1.6.17",
4
4
  "description": "Technical specification drafting tool that generates rich specification documents from markdown. Forked from https://github.com/decentralized-identity/spec-up by Daniel Buchner (https://github.com/csuwildcat)",
5
5
  "main": "./index",
6
6
  "repository": {
@@ -30,10 +30,17 @@ async function updateGitignore(gitignorePath, filesToAdd) {
30
30
  // Split the content into lines and remove empty lines
31
31
  const gitignoreLines = gitignoreContent.split('\n').filter(line => line.trim() !== '');
32
32
 
33
- // Add files to .gitignore if they are not already present
33
+ // Add files to .gitignore if they are not already present.
34
+ // Strip inline comments from existing lines before comparing, so that
35
+ // entries like "*.lnk # some comment" are not duplicated.
34
36
  filesToAdd.forEach(file => {
35
- if (!gitignoreLines.some(line => line.trim() === file.trim())) {
36
- gitignoreLines.push(file.trim());
37
+ const pattern = file.trim();
38
+ const alreadyPresent = gitignoreLines.some(line => {
39
+ const linePattern = line.trim().split('#')[0].trim();
40
+ return linePattern === pattern;
41
+ });
42
+ if (!alreadyPresent) {
43
+ gitignoreLines.push(pattern);
37
44
  }
38
45
  });
39
46
 
@@ -3,19 +3,41 @@ const path = require('path');
3
3
  // Configuration
4
4
  const gitIgnoreEntries = {
5
5
  gitignorePath: path.join(process.cwd(), '.gitignore'),
6
- filesToAdd: ['node_modules',
6
+ filesToAdd: [
7
+ // Generated by render-and-deploy.yml — do not commit build output
8
+ 'docs/',
9
+ // Dependencies
10
+ 'node_modules/',
11
+ // Logs
7
12
  '*.log',
8
- 'dist',
13
+ // Editor / OS temporaries & backups
9
14
  '*.bak',
10
15
  '*.tmp',
16
+ '.idea',
17
+ '.vscode/',
18
+ // Environment / secrets
19
+ '.env*',
20
+ // Test coverage
21
+ 'coverage/',
22
+ // Various caches & history
23
+ '.history/',
24
+ '.cache/',
25
+ // macOS
11
26
  '.DS_Store',
12
- '.env',
13
- 'coverage',
14
- 'build',
15
- '.history',
16
- // Generated by render-and-deploy.yml — do not commit build output
17
- 'docs'
18
- ],
27
+ '.AppleDouble',
28
+ '.LSOverride',
29
+ // Windows
30
+ 'Thumbs.db',
31
+ 'ehthumbs.db',
32
+ 'Desktop.ini',
33
+ '$RECYCLE.BIN/',
34
+ '*.lnk',
35
+ // Linux / Ubuntu / general Unix-like
36
+ '*~',
37
+ '.*.swp',
38
+ '.*.swo',
39
+ '.fuse_hidden*',
40
+ ],
19
41
  };
20
42
 
21
43
  module.exports = { gitIgnoreEntries };
@@ -18,6 +18,34 @@ const fs = require('fs-extra');
18
18
  const path = require('path');
19
19
  const Logger = require('../utils/logger');
20
20
 
21
+ /**
22
+ * Extracts snapshot labels from a hand-edited docs/versions/index.html.
23
+ * Parses every anchor whose href points to a version directory (e.g. "v1/")
24
+ * and maps the directory name to the visible link text.
25
+ *
26
+ * Example input: <a href="v1/">KERI Release 1.0</a>
27
+ * Example output: { "v1": "KERI Release 1.0" }
28
+ *
29
+ * The "Latest version" link (href="../") is intentionally ignored.
30
+ *
31
+ * @param {string} indexHtmlPath - Absolute path to docs/versions/index.html.
32
+ * @returns {Object} Map of version directory names to label strings.
33
+ */
34
+ function extractLabelsFromIndexHtml(indexHtmlPath) {
35
+ const labels = {};
36
+ const html = fs.readFileSync(indexHtmlPath, 'utf8');
37
+ // Match: href="v1/" or href="v1" (with or without trailing slash)
38
+ const linkPattern = /href="(v\d+)\/?">([^<]+)<\/a>/g;
39
+ let match = linkPattern.exec(html);
40
+ while (match !== null) {
41
+ const dirName = match[1];
42
+ const linkText = match[2].trim();
43
+ labels[dirName] = linkText;
44
+ match = linkPattern.exec(html);
45
+ }
46
+ return labels;
47
+ }
48
+
21
49
  /**
22
50
  * Migrates frozen snapshots from docs/versions/ to snapshots/.
23
51
  * @param {string} outputPath - The output directory defined in specs.json (e.g. './docs').
@@ -57,27 +85,38 @@ function migrateVersionsToSnapshots(outputPath) {
57
85
  });
58
86
 
59
87
  // Always keep labels.json up to date in snapshots/.
88
+ // Priority order (highest wins):
89
+ // 1. Labels already in snapshots/labels.json (from previous migrations or freezes)
90
+ // 2. Labels extracted from hand-edited docs/versions/index.html
91
+ // 3. Labels from docs/versions/labels.json (auto-generated source)
60
92
  const srcLabels = path.join(src, 'labels.json');
93
+ const srcIndexHtml = path.join(src, 'index.html');
61
94
  const destLabels = path.join(dest, 'labels.json');
62
95
 
63
- if (fs.existsSync(srcLabels)) {
64
- if (!fs.existsSync(destLabels)) {
65
- // No labels.json in snapshots/ yet — copy the whole file.
66
- fs.copySync(srcLabels, destLabels);
67
- } else {
68
- // Merge: preserve any entries already in snapshots/labels.json
69
- // and add any entries from docs/versions/labels.json that are missing.
70
- const existing = fs.readJsonSync(destLabels);
71
- const legacy = fs.readJsonSync(srcLabels);
72
- const merged = { ...legacy, ...existing }; // snapshots wins on conflict
73
- fs.writeJsonSync(destLabels, merged, { spaces: 2 });
74
- }
96
+ // Start from labels.json if it exists
97
+ const legacyLabels = fs.existsSync(srcLabels) ? fs.readJsonSync(srcLabels) : {};
98
+
99
+ // Overlay with labels parsed from index.html — these may be hand-edited
100
+ const htmlLabels = fs.existsSync(srcIndexHtml)
101
+ ? extractLabelsFromIndexHtml(srcIndexHtml)
102
+ : {};
103
+
104
+ // Existing snapshots/labels.json wins on any conflict
105
+ const existingDestLabels = fs.existsSync(destLabels) ? fs.readJsonSync(destLabels) : {};
106
+
107
+ const merged = { ...legacyLabels, ...htmlLabels, ...existingDestLabels };
108
+
109
+ if (Object.keys(merged).length > 0) {
110
+ fs.writeJsonSync(destLabels, merged, { spaces: 2 });
75
111
  }
76
112
 
77
113
  if (migratedCount > 0) {
78
- Logger.success(
79
- `Migrated ${migratedCount} snapshot(s) from ${src} to ${dest}/. ` +
80
- `You can now safely run: git rm -r --cached ${outputPath}`
114
+ Logger.action(
115
+ `Migrated ${migratedCount} snapshot(s) from ${src} to ${dest}/.`,
116
+ {
117
+ hint: `git rm -r --cached ${outputPath}`,
118
+ context: 'Remove old docs from git tracking'
119
+ }
81
120
  );
82
121
  }
83
122
  }
@@ -131,6 +131,66 @@ class Logger {
131
131
  console.log(); // Extra newline for readability
132
132
  }
133
133
 
134
+ /**
135
+ * Action required messages - red with pointing hand icon
136
+ *
137
+ * Indicates that the user must take action to proceed.
138
+ * More urgent than a warning, used when user intervention is required.
139
+ *
140
+ * @param {string} message - The main action required message
141
+ * @param {...any} args - Additional arguments. Can include:
142
+ * - Regular values (strings, numbers, objects) for message formatting
143
+ * - An options object (if last arg is object with 'hint', 'context', or 'details' keys):
144
+ * - hint: Clear instruction on what action to take
145
+ * - context: Additional context about why action is needed
146
+ * - details: Technical details or related information
147
+ *
148
+ * @example
149
+ * Logger.action('Configuration required before proceeding', {
150
+ * context: 'specs.json is incomplete',
151
+ * hint: 'Add the "title" and "github_url" fields to specs.json',
152
+ * details: 'See https://example.com/docs'
153
+ * });
154
+ */
155
+ static action(message, ...args) {
156
+ // Extract options object if present (last arg with special keys)
157
+ const lastArg = args[args.length - 1];
158
+ const isOptionsObject = lastArg && typeof lastArg === 'object' &&
159
+ (lastArg.hint || lastArg.context || lastArg.details);
160
+
161
+ const options = isOptionsObject ? args.pop() : {};
162
+ const regularArgs = args;
163
+
164
+ // Display main action required message - red to signal urgency
165
+ console.log(chalk.red('👉'), chalk.red(message), ...regularArgs);
166
+
167
+ // Display context if provided - explains why action is needed
168
+ if (options.context) {
169
+ console.log(chalk.red(' Context:'), chalk.gray(options.context));
170
+ }
171
+
172
+ // Display technical details if provided - helps understand the situation
173
+ if (options.details) {
174
+ const detailsStr = typeof options.details === 'object'
175
+ ? JSON.stringify(options.details, null, 2)
176
+ : String(options.details);
177
+ console.log(chalk.red(' Details:'), chalk.gray(detailsStr));
178
+ }
179
+
180
+ // Display hint if provided - critical instruction on what to do
181
+ if (options.hint) {
182
+ console.log(chalk.red(' 👉 Action:'), chalk.red(options.hint));
183
+ }
184
+
185
+ // Collect message with all context for healthcheck/JSON output
186
+ messageCollector.addMessage('action', message, [...regularArgs, options]);
187
+
188
+ console.log(); // Extra newline for readability
189
+ }
190
+
191
+ /**
192
+ * Information messages - blue
193
+ */
134
194
  static info(message, ...args) {
135
195
  console.log(chalk.blue('📋'), chalk.blue(message), ...args);
136
196
  messageCollector.addMessage('info', message, args);