@wipcomputer/wip-release 1.9.14 → 1.9.15

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/SKILL.md +19 -1
  2. package/cli.js +2 -1
  3. package/core.mjs +26 -6
  4. package/package.json +1 -1
package/SKILL.md CHANGED
@@ -78,10 +78,28 @@ wip-release checks that product docs (dev update, roadmap, readme-first) were up
78
78
  - **--skip-product-check**: bypasses the gate
79
79
 
80
80
  Checks:
81
- 1. `ai/dev-updates/` has a file from the last 3 days
81
+ 1. `ai/dev-updates/` has a file modified since the last release tag
82
82
  2. `ai/product/plans-prds/roadmap.md` was modified since last release
83
83
  3. `ai/product/readme-first-product.md` was modified since last release
84
84
 
85
+ ### Skill Publish to Website
86
+
87
+ After publishing, wip-release auto-copies SKILL.md to your website as plain text. Any AI can fetch the URL and get clean install instructions.
88
+
89
+ **Setup:** Set the `WIP_WEBSITE_REPO` environment variable to your website repo path. That's it.
90
+
91
+ **How it works:**
92
+ 1. If SKILL.md exists and `WIP_WEBSITE_REPO` is set, copies SKILL.md to `{website}/wip.computer/install/{name}.txt`
93
+ 2. Runs `deploy.sh` in the website repo to push live
94
+ 3. Non-blocking: if deploy fails, the release still succeeds
95
+
96
+ **Name resolution (first match wins):**
97
+ 1. `.publish-skill.json` in repo root: `{ "name": "my-tool" }`
98
+ 2. `package.json` name (with `@scope/` prefix stripped)
99
+ 3. Directory name (with `-private` suffix stripped)
100
+
101
+ No config file needed. Every repo with a SKILL.md auto-publishes on release.
102
+
85
103
  ### Module
86
104
 
87
105
  ```javascript
package/cli.js CHANGED
@@ -23,7 +23,8 @@ const skipStaleCheck = args.includes('--skip-stale-check');
23
23
  const skipWorktreeCheck = args.includes('--skip-worktree-check');
24
24
  const notesFilePath = flag('notes-file');
25
25
  let notes = flag('notes');
26
- let notesSource = notes ? 'flag' : 'none'; // track where notes came from
26
+ // Bug fix #121: use strict check, not truthiness. --notes="" is empty, not absent.
27
+ let notesSource = (notes !== null && notes !== undefined && notes !== '') ? 'flag' : 'none';
27
28
 
28
29
  // Release notes priority (highest wins):
29
30
  // 1. --notes-file=path Explicit file path (always wins)
package/core.mjs CHANGED
@@ -90,19 +90,27 @@ export function syncSkillVersion(repoPath, newVersion) {
90
90
  export function updateChangelog(repoPath, newVersion, notes) {
91
91
  const changelogPath = join(repoPath, 'CHANGELOG.md');
92
92
  const date = new Date().toISOString().split('T')[0];
93
- const entry = `## ${newVersion} (${date})\n\n${notes || 'Release.'}\n`;
93
+
94
+ // Bug fix #121: never silently default to "Release." when notes are empty.
95
+ // If notes are empty at this point, warn loudly.
96
+ if (!notes || !notes.trim()) {
97
+ console.warn(` ! WARNING: No release notes provided for v${newVersion}. CHANGELOG entry will be minimal.`);
98
+ notes = 'No release notes provided.';
99
+ }
100
+
101
+ const entry = `## ${newVersion} (${date})\n\n${notes}\n`;
94
102
 
95
103
  if (!existsSync(changelogPath)) {
96
- writeFileSync(changelogPath, `# Changelog\n\n${entry}\n`);
104
+ writeFileSync(changelogPath, `# Changelog\n\n${entry}`);
97
105
  return;
98
106
  }
99
107
 
100
108
  let content = readFileSync(changelogPath, 'utf8');
101
- // Insert after the # Changelog header
102
- const headerMatch = content.match(/^# Changelog\s*\n/);
109
+ // Insert after the # Changelog header (single newline, no accumulation)
110
+ const headerMatch = content.match(/^# Changelog\s*\n+/);
103
111
  if (headerMatch) {
104
112
  const insertPoint = headerMatch[0].length;
105
- content = content.slice(0, insertPoint) + '\n' + entry + '\n' + content.slice(insertPoint);
113
+ content = content.slice(0, insertPoint) + entry + '\n' + content.slice(insertPoint);
106
114
  } else {
107
115
  content = `# Changelog\n\n${entry}\n${content}`;
108
116
  }
@@ -395,7 +403,7 @@ export function buildReleaseNotes(repoPath, currentVersion, newVersion, notes) {
395
403
  }
396
404
 
397
405
  // Install section
398
- lines.push('### Install\n');
406
+ lines.push('### Install');
399
407
  lines.push('```bash');
400
408
  lines.push(`npm install -g ${pkg.name}@${newVersion}`);
401
409
  lines.push('```');
@@ -438,6 +446,18 @@ export function createGitHubRelease(repoPath, newVersion, notes, currentVersion)
438
446
  '--notes-file', '.release-notes-tmp.md',
439
447
  '--repo', repoSlug
440
448
  ], { cwd: repoPath, stdio: 'inherit' });
449
+
450
+ // Bug fix #121: verify the release was actually created
451
+ try {
452
+ const verify = execFileSync('gh', [
453
+ 'release', 'view', `v${newVersion}`,
454
+ '--repo', repoSlug, '--json', 'body', '--jq', '.body | length'
455
+ ], { cwd: repoPath, encoding: 'utf8' }).trim();
456
+ const bodyLen = parseInt(verify, 10);
457
+ if (bodyLen < 50) {
458
+ console.warn(` ! GitHub release body is only ${bodyLen} chars. Notes may be truncated.`);
459
+ }
460
+ } catch {}
441
461
  } finally {
442
462
  try { execFileSync('rm', ['-f', tmpFile]); } catch {}
443
463
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-release",
3
- "version": "1.9.14",
3
+ "version": "1.9.15",
4
4
  "type": "module",
5
5
  "description": "One-command release pipeline. Bumps version, updates changelog + SKILL.md, publishes to npm + GitHub.",
6
6
  "main": "core.mjs",