wds-add-spacing.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // wds-add-spacing.js — WDS scaffold: append a spacing object to a page spec file
  2. // Usage: node src/scripts/wds-add-spacing.js --page "C-UX-Scenarios/01-onboarding/01-start/01-start.md" \
  3. // --direction v --type space --size xl --reason "major section boundary between hero and features"
  4. 'use strict';
  5. const fs = require('node:fs');
  6. const path = require('node:path');
  7. function parseArgs(argv) {
  8. const args = {};
  9. for (let i = 0; i < argv.length; i++) {
  10. if (argv[i].startsWith('--')) {
  11. const key = argv[i].slice(2);
  12. const value = argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[i + 1] : true;
  13. args[key] = value;
  14. if (value !== true) i++;
  15. }
  16. }
  17. return args;
  18. }
  19. function printUsage() {
  20. process.stdout.write(
  21. [
  22. 'Usage: node src/scripts/wds-add-spacing.js --page <path> --direction <v|h> --type <type> --size <size> [options]',
  23. '',
  24. 'Required:',
  25. ' --page Path to the page spec .md file',
  26. ' --direction v (vertical) or h (horizontal)',
  27. ' --type space | separator | line',
  28. ' --size zero | sm | md | lg | xl | 2xl | 3xl | flex',
  29. '',
  30. 'Optional:',
  31. ' --reason Why this spacing exists',
  32. '',
  33. 'Valid directions: v, h',
  34. 'Valid types: space, separator, line',
  35. 'Valid sizes: zero, sm, md, lg, xl, 2xl, 3xl, flex',
  36. '',
  37. ].join('\n'),
  38. );
  39. }
  40. const VALID_DIRECTIONS = ['v', 'h'];
  41. const VALID_TYPES = ['space', 'separator', 'line'];
  42. const VALID_SIZES = ['zero', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', 'flex'];
  43. // Derive page prefix from slug: "01-start" -> "start"
  44. function pagePrefix(slug) {
  45. const parts = slug.split('-');
  46. return parts.length > 1 ? parts.slice(1).join('-') : slug;
  47. }
  48. function pageSlugFromPath(filePath) {
  49. return path.basename(filePath, '.md');
  50. }
  51. function buildSpacingBlock(spacingId, reason) {
  52. const icon = '↕';
  53. const reasonText = reason ? ` — ${reason}` : '';
  54. return `#### ${icon} \`${spacingId}\`${reasonText}\n`;
  55. }
  56. function appendToSpacingSection(content, spacingBlock) {
  57. const lines = content.split('\n');
  58. const spacingIdx = lines.findIndex((l) => l.trim() === '## Spacing');
  59. if (spacingIdx === -1) {
  60. // No spacing section — append before first ## after metadata
  61. return content + `\n## Spacing\n\n${spacingBlock}\n`;
  62. }
  63. // Find end of spacing section (next ## or ---)
  64. let endIdx = spacingIdx + 1;
  65. while (endIdx < lines.length) {
  66. const t = lines[endIdx].trim();
  67. if ((t.startsWith('## ') && t !== '## Spacing') || t === '---') break;
  68. endIdx++;
  69. }
  70. // Insert just before the end marker
  71. const before = lines.slice(0, endIdx);
  72. const after = lines.slice(endIdx);
  73. return [...before, spacingBlock, ...after].join('\n');
  74. }
  75. function main() {
  76. const args = parseArgs(process.argv.slice(2));
  77. if (args.help) {
  78. printUsage();
  79. process.exit(0);
  80. }
  81. if (!args.page || !args.direction || !args.type || args.size === 0) {
  82. process.stderr.write('Error: --page, --direction, --type, and --size are required.\n\n');
  83. printUsage();
  84. process.exit(1);
  85. }
  86. if (!VALID_DIRECTIONS.includes(args.direction)) {
  87. process.stderr.write(`Error: Invalid direction "${args.direction}". Must be: ${VALID_DIRECTIONS.join(', ')}\n`);
  88. process.exit(1);
  89. }
  90. if (!VALID_TYPES.includes(args.type)) {
  91. process.stderr.write(`Error: Invalid type "${args.type}". Must be: ${VALID_TYPES.join(', ')}\n`);
  92. process.exit(1);
  93. }
  94. if (!VALID_SIZES.includes(args.size)) {
  95. process.stderr.write(`Error: Invalid size "${args.size}". Must be: ${VALID_SIZES.join(', ')}\n`);
  96. process.exit(1);
  97. }
  98. const filePath = path.resolve(args.page);
  99. if (!fs.existsSync(filePath)) {
  100. process.stderr.write(`Error: File not found: ${filePath}\n`);
  101. process.exit(1);
  102. }
  103. const slug = pageSlugFromPath(filePath);
  104. const prefix = pagePrefix(slug);
  105. const spacingId = `${prefix}-${args.direction}-${args.type}-${args.size}`;
  106. const reason = args.reason || '';
  107. let content;
  108. try {
  109. content = fs.readFileSync(filePath, 'utf8');
  110. } catch (error) {
  111. process.stderr.write(`Error reading file: ${error.message}\n`);
  112. process.exit(1);
  113. }
  114. // Check for duplicate spacing ID
  115. if (content.includes(`\`${spacingId}\``)) {
  116. process.stderr.write(`Error: Spacing ID already exists in file: ${spacingId}\n`);
  117. process.stderr.write('Use a different combination of direction/type/size or manually edit the file.\n');
  118. process.exit(1);
  119. }
  120. const spacingBlock = buildSpacingBlock(spacingId, reason);
  121. const updated = appendToSpacingSection(content, spacingBlock);
  122. try {
  123. fs.writeFileSync(filePath, updated, 'utf8');
  124. } catch (error) {
  125. process.stderr.write(`Error writing file: ${error.message}\n`);
  126. process.exit(1);
  127. }
  128. process.stdout.write(`✓ Added spacing object ${spacingId}\n`);
  129. process.stdout.write(` File: ${filePath}\n`);
  130. }
  131. main();