@wipcomputer/wip-release 1.9.12 → 1.9.14

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 (2) hide show
  1. package/core.mjs +76 -41
  2. package/package.json +1 -1
package/core.mjs CHANGED
@@ -273,19 +273,23 @@ function checkProductDocs(repoPath) {
273
273
  const aiDir = join(repoPath, 'ai');
274
274
  if (!existsSync(aiDir)) return { missing: [], ok: true, skipped: true };
275
275
 
276
- // 1. Dev update: file from today (or last 3 days)
276
+ // 1. Dev update: must have a file modified since last release tag.
277
+ // Old check ("any file from last 3 days") let the same stale file pass
278
+ // across 11 releases in one session. Now uses the same git-based check
279
+ // as roadmap and readme-first: was the file actually changed since the tag?
277
280
  const devUpdatesDir = join(aiDir, 'dev-updates');
278
281
  if (existsSync(devUpdatesDir)) {
279
- const now = new Date();
280
- const recentDates = [];
281
- for (let i = 0; i < 3; i++) {
282
- const d = new Date(now);
283
- d.setDate(d.getDate() - i);
284
- recentDates.push(d.toISOString().split('T')[0]);
285
- }
286
282
  const files = readdirSync(devUpdatesDir).filter(f => f.endsWith('.md'));
287
- const hasRecent = files.some(f => recentDates.some(d => f.startsWith(d)));
288
- if (!hasRecent) missing.push('ai/dev-updates/ (no dev update from last 3 days)');
283
+ if (files.length === 0) {
284
+ missing.push('ai/dev-updates/ (no dev update files)');
285
+ } else {
286
+ const anyModified = files.some(f =>
287
+ fileModifiedSinceLastTag(repoPath, `ai/dev-updates/${f}`)
288
+ );
289
+ if (!anyModified) {
290
+ missing.push('ai/dev-updates/ (no dev update modified since last release)');
291
+ }
292
+ }
289
293
  }
290
294
 
291
295
  // 2. Roadmap: modified since last tag
@@ -463,31 +467,23 @@ export function publishClawHub(repoPath, newVersion, notes) {
463
467
  /**
464
468
  * Publish SKILL.md to website as plain text.
465
469
  *
466
- * Reads .publish-skill.json from repo root:
467
- * { "name": "memory-crystal" }
470
+ * Auto-detects: if SKILL.md exists and WIP_WEBSITE_REPO is set,
471
+ * publishes automatically. No config file needed.
472
+ *
473
+ * Name resolution (first match wins):
474
+ * 1. .publish-skill.json { "name": "memory-crystal" }
475
+ * 2. SKILL.md frontmatter name: field
476
+ * 3. Directory name (basename of repoPath)
468
477
  *
469
- * Uses WIP_WEBSITE_REPO env var for website repo path.
470
478
  * Copies SKILL.md to {website}/wip.computer/install/{name}.txt
471
479
  * Then runs deploy.sh to push to VPS.
472
480
  *
473
481
  * Non-blocking: returns result, never throws.
474
482
  */
475
483
  export function publishSkillToWebsite(repoPath) {
476
- const configPath = join(repoPath, '.publish-skill.json');
477
- if (!existsSync(configPath)) return { skipped: true, reason: 'no .publish-skill.json' };
478
-
479
484
  const websiteRepo = process.env.WIP_WEBSITE_REPO;
480
485
  if (!websiteRepo) return { skipped: true, reason: 'WIP_WEBSITE_REPO not set' };
481
486
 
482
- let config;
483
- try {
484
- config = JSON.parse(readFileSync(configPath, 'utf8'));
485
- } catch (e) {
486
- return { ok: false, error: `bad .publish-skill.json: ${e.message}` };
487
- }
488
-
489
- if (!config.name) return { ok: false, error: '.publish-skill.json missing "name"' };
490
-
491
487
  // Find SKILL.md: check root, then skills/*/SKILL.md
492
488
  let skillFile = join(repoPath, 'SKILL.md');
493
489
  if (!existsSync(skillFile)) {
@@ -499,7 +495,37 @@ export function publishSkillToWebsite(repoPath) {
499
495
  }
500
496
  }
501
497
  }
502
- if (!existsSync(skillFile)) return { ok: false, error: 'no SKILL.md found' };
498
+ if (!existsSync(skillFile)) return { skipped: true, reason: 'no SKILL.md found' };
499
+
500
+ // Resolve target name: config > package.json > directory name
501
+ // SKILL.md frontmatter name is skipped because it's a short slug
502
+ // (e.g., "memory") not the full install name (e.g., "memory-crystal").
503
+ let targetName;
504
+
505
+ // 1. Explicit config (optional, overrides auto-detect)
506
+ const configPath = join(repoPath, '.publish-skill.json');
507
+ if (existsSync(configPath)) {
508
+ try {
509
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
510
+ if (config.name) targetName = config.name;
511
+ } catch {}
512
+ }
513
+
514
+ // 2. package.json name (strip @scope/ prefix, most reliable)
515
+ if (!targetName) {
516
+ const pkgPath = join(repoPath, 'package.json');
517
+ if (existsSync(pkgPath)) {
518
+ try {
519
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
520
+ if (pkg.name) targetName = pkg.name.replace(/^@[^/]+\//, '');
521
+ } catch {}
522
+ }
523
+ }
524
+
525
+ // 3. Directory name fallback (strip -private suffix)
526
+ if (!targetName) {
527
+ targetName = basename(repoPath).replace(/-private$/, '').toLowerCase();
528
+ }
503
529
 
504
530
  // Copy to website install dir
505
531
  const installDir = join(websiteRepo, 'wip.computer', 'install');
@@ -507,7 +533,7 @@ export function publishSkillToWebsite(repoPath) {
507
533
  try { mkdirSync(installDir, { recursive: true }); } catch {}
508
534
  }
509
535
 
510
- const targetFile = join(installDir, `${config.name}.txt`);
536
+ const targetFile = join(installDir, `${targetName}.txt`);
511
537
  try {
512
538
  const content = readFileSync(skillFile, 'utf8');
513
539
  writeFileSync(targetFile, content);
@@ -521,13 +547,13 @@ export function publishSkillToWebsite(repoPath) {
521
547
  try {
522
548
  execSync(`bash deploy.sh`, { cwd: websiteRepo, stdio: 'pipe', timeout: 30000 });
523
549
  } catch (e) {
524
- return { ok: true, deployed: false, target: config.name, error: `deploy failed: ${e.message}` };
550
+ return { ok: true, deployed: false, target: targetName, error: `deploy failed: ${e.message}` };
525
551
  }
526
552
  } else {
527
- return { ok: true, deployed: false, target: config.name, error: 'no deploy.sh found' };
553
+ return { ok: true, deployed: false, target: targetName, error: 'no deploy.sh found' };
528
554
  }
529
555
 
530
- return { ok: true, deployed: true, target: config.name };
556
+ return { ok: true, deployed: true, target: targetName };
531
557
  }
532
558
 
533
559
  // ── Helpers ──────────────────────────────────────────────────────────
@@ -817,18 +843,27 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
817
843
  console.log(` [dry run] Would publish to GitHub Packages`);
818
844
  console.log(` [dry run] Would create GitHub release v${newVersion}`);
819
845
  if (hasSkill) console.log(` [dry run] Would publish to ClawHub`);
820
- // Skill-to-website dry run
821
- const publishConfig = join(repoPath, '.publish-skill.json');
822
- if (existsSync(publishConfig)) {
823
- try {
824
- const pc = JSON.parse(readFileSync(publishConfig, 'utf8'));
825
- const envSet = !!process.env.WIP_WEBSITE_REPO;
826
- if (pc.name && envSet) {
827
- console.log(` [dry run] Would publish SKILL.md to website: install/${pc.name}.txt`);
828
- } else if (pc.name && !envSet) {
829
- console.log(` [dry run] Would publish to website but WIP_WEBSITE_REPO not set`);
846
+ // Skill-to-website dry run (auto-detects SKILL.md, no config needed)
847
+ if (hasSkill) {
848
+ const envSet = !!process.env.WIP_WEBSITE_REPO;
849
+ if (envSet) {
850
+ // Resolve name same way as publishSkillToWebsite
851
+ let dryName;
852
+ const publishConfig = join(repoPath, '.publish-skill.json');
853
+ if (existsSync(publishConfig)) {
854
+ try { dryName = JSON.parse(readFileSync(publishConfig, 'utf8')).name; } catch {}
830
855
  }
831
- } catch {}
856
+ if (!dryName) {
857
+ const pkgPath = join(repoPath, 'package.json');
858
+ if (existsSync(pkgPath)) {
859
+ try { dryName = JSON.parse(readFileSync(pkgPath, 'utf8')).name?.replace(/^@[^/]+\//, ''); } catch {}
860
+ }
861
+ }
862
+ if (!dryName) dryName = basename(repoPath).replace(/-private$/, '').toLowerCase();
863
+ console.log(` [dry run] Would publish SKILL.md to website: install/${dryName}.txt`);
864
+ } else {
865
+ console.log(` [dry run] Would publish SKILL.md to website but WIP_WEBSITE_REPO not set`);
866
+ }
832
867
  }
833
868
  }
834
869
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-release",
3
- "version": "1.9.12",
3
+ "version": "1.9.14",
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",