spec-up-t 1.6.9 → 1.6.11

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.9",
3
+ "version": "1.6.11",
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": {
@@ -2,10 +2,13 @@
2
2
  * @file freeze-spec-data.js
3
3
  * @description Reads the output path from specs.json, finds the highest versioned directory
4
4
  * in the destination path, and copies index.html to a new directory with an incremented version.
5
+ * The user is prompted for a human-readable label for the snapshot; the label is persisted in
6
+ * versions/labels.json so that create-versions-index.js can use it as link text.
5
7
  */
6
8
 
7
9
  const fs = require('fs-extra');
8
10
  const path = require('path');
11
+ const readline = require('node:readline');
9
12
  const Logger = require('./utils/logger');
10
13
  const { versions } = require('./utils/regex-patterns');
11
14
 
@@ -15,6 +18,16 @@ const outputPath = config.specs[0].output_path;
15
18
  const sourceFile = path.join(outputPath, 'index.html');
16
19
  const destDir = path.join(outputPath, 'versions');
17
20
 
21
+ // A snapshot can only be created when the specification has been built at least once.
22
+ // If index.html is missing, the user must run `npm run menu 1` (or `npm run menu 4`) first.
23
+ if (!fs.existsSync(sourceFile)) {
24
+ Logger.error(
25
+ `Cannot create a snapshot: "${sourceFile}" does not exist.\n` +
26
+ `Please build the specification first (e.g. npm run menu 1 or npm run menu 9) and try again.`
27
+ );
28
+ process.exit(0);
29
+ }
30
+
18
31
  if (!fs.existsSync(destDir)) {
19
32
  fs.mkdirSync(destDir, { recursive: true });
20
33
  }
@@ -35,16 +48,78 @@ dirs.forEach(dir => {
35
48
 
36
49
  const newVersion = highestVersion + 1;
37
50
  const newVersionDir = path.join(destDir, `v${newVersion}`);
51
+ const defaultLabel = `v${newVersion}`;
38
52
 
39
- if (!fs.existsSync(newVersionDir)) {
40
- fs.mkdirSync(newVersionDir, { recursive: true });
53
+ /**
54
+ * Prompts the user for a single line of input, showing a default value.
55
+ * Pressing Enter without typing accepts the default.
56
+ * @param {string} question - The question text shown to the user.
57
+ * @param {string} defaultValue - The value used when the user presses Enter without typing.
58
+ * @returns {Promise<string>}
59
+ */
60
+ function prompt(question, defaultValue) {
61
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
62
+ return new Promise(resolve => {
63
+ rl.question(`${question} [${defaultValue}]: `, answer => {
64
+ rl.close();
65
+ resolve(answer.trim() || defaultValue);
66
+ });
67
+ });
41
68
  }
42
69
 
43
- const destFile = path.join(newVersionDir, 'index.html');
44
- fs.copyFileSync(sourceFile, destFile);
70
+ /**
71
+ * Persists the label for the given directory name in versions/labels.json.
72
+ * Existing entries are preserved; only the new key is added or updated.
73
+ * @param {string} labelsFile - Absolute path to labels.json.
74
+ * @param {string} dirName - The version directory name (e.g. "v3").
75
+ * @param {string} label - The human-readable label to store.
76
+ */
77
+ function saveLabel(labelsFile, dirName, label) {
78
+ const existing = fs.existsSync(labelsFile) ? fs.readJsonSync(labelsFile) : {};
79
+ existing[dirName] = label;
80
+ fs.writeJsonSync(labelsFile, existing, { spaces: 2 });
81
+ }
82
+
83
+ /**
84
+ * Main flow: resolve the label, create the version directory, copy the snapshot,
85
+ * save the label, and regenerate the versions index.
86
+ *
87
+ * Label resolution order:
88
+ * 1. FREEZE_LABEL environment variable — set by the menu.yml GitHub Actions workflow
89
+ * so that GitHubUi (or any non-interactive caller) can supply a custom label.
90
+ * 2. Interactive readline prompt — only used when stdin is a real TTY (local CLI).
91
+ * 3. Default label (e.g. "v3") — fallback for non-interactive environments without
92
+ * an explicit FREEZE_LABEL (e.g. running freeze directly inside a CI script).
93
+ */
94
+ async function run() {
95
+ let label;
96
+
97
+ if (process.env.FREEZE_LABEL) {
98
+ // Non-interactive path: label supplied by caller via environment variable
99
+ label = process.env.FREEZE_LABEL;
100
+ } else if (process.stdin.isTTY) {
101
+ // Interactive path: prompt the user, defaulting to the auto-generated name
102
+ label = await prompt('Enter a label for this snapshot', defaultLabel);
103
+ } else {
104
+ // Non-interactive path without an explicit label: use the auto-generated default
105
+ label = defaultLabel;
106
+ }
107
+
108
+ if (!fs.existsSync(newVersionDir)) {
109
+ fs.mkdirSync(newVersionDir, { recursive: true });
110
+ }
45
111
 
46
- Logger.success(`Created a freezed specification version in ${destFile}`);
112
+ const destFile = path.join(newVersionDir, 'index.html');
113
+ fs.copyFileSync(sourceFile, destFile);
114
+
115
+ const labelsFile = path.join(destDir, 'labels.json');
116
+ saveLabel(labelsFile, `v${newVersion}`, label);
117
+
118
+ Logger.success(`Created a freezed specification version in ${destFile}`);
119
+
120
+ // Update the versions index.html to include the newly created version
121
+ const createVersionsIndex = require('./pipeline/configuration/create-versions-index.js');
122
+ createVersionsIndex(outputPath);
123
+ }
47
124
 
48
- // Update the versions index.html to include the newly created version
49
- const createVersionsIndex = require('./pipeline/configuration/create-versions-index.js');
50
- createVersionsIndex(outputPath);
125
+ run();
@@ -24,12 +24,17 @@ on:
24
24
  triggered_by:
25
25
  description: 'Triggered by'
26
26
  required: false
27
+ freeze_label:
28
+ description: 'Label for the snapshot (freeze action only). Leave empty to use the auto-generated version number.'
29
+ required: false
30
+ default: ''
27
31
 
28
32
  jobs:
29
33
  build-and-deploy-spec:
30
34
  runs-on: ubuntu-latest
31
35
  permissions:
32
- contents: write # Needed for pushing changes and deploying to Pages
36
+ contents: write # Needed for pushing changes
37
+ actions: write # Needed to dispatch render-and-deploy.yml
33
38
  steps:
34
39
  - name: Checkout 🛎️
35
40
  uses: actions/checkout@v4
@@ -64,7 +69,14 @@ jobs:
64
69
  run: |
65
70
  case "${{ github.event.inputs.action_type }}" in
66
71
  render)
67
- npm run render
72
+ # Rendering is handled by render-and-deploy.yml.
73
+ # Dispatch that workflow to render and deploy to GitHub Pages.
74
+ curl -s -X POST \
75
+ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
76
+ -H "Accept: application/vnd.github+json" \
77
+ "https://api.github.com/repos/${{ github.repository }}/actions/workflows/render-and-deploy.yml/dispatches" \
78
+ -d "{\"ref\":\"${{ github.ref_name }}\"}"
79
+ echo "render-and-deploy.yml dispatched"
68
80
  ;;
69
81
  topdf)
70
82
  npm run topdf
@@ -73,7 +85,9 @@ jobs:
73
85
  npm run todocx
74
86
  ;;
75
87
  freeze)
76
- npm run freeze
88
+ # Pass the label to freeze-spec-data.js via environment variable.
89
+ # If freeze_label is empty the script falls back to the auto-generated default.
90
+ FREEZE_LABEL="${{ github.event.inputs.freeze_label }}" npm run freeze
77
91
  ;;
78
92
  custom-update)
79
93
  npm run custom-update
@@ -99,7 +113,8 @@ jobs:
99
113
  # Commit with appropriate message
100
114
  case "${{ github.event.inputs.action_type }}" in
101
115
  render)
102
- git commit -m "Render specification: Update files" || echo "No changes to commit"
116
+ # render-and-deploy.yml already handles the commit/deploy; nothing to commit here.
117
+ echo "Render dispatched to render-and-deploy.yml — no direct commit needed"
103
118
  ;;
104
119
  topdf)
105
120
  git commit -m "Export to PDF" || echo "No changes to commit"
@@ -122,16 +137,7 @@ jobs:
122
137
  esac
123
138
 
124
139
  # Push changes
125
- git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:main
126
-
127
- - name: Commit output files
128
- if: success()
129
- run: |
130
- git config --global user.email "actions@github.com"
131
- git config --global user.name "GitHub Actions"
132
- git add "$OUTPUT_PATH"
133
- git commit -m "Update output files in $OUTPUT_PATH" || echo "No changes to commit in output directory"
134
- git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:main
140
+ git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:${{ github.ref_name }}
135
141
 
136
142
  - name: Clean up
137
143
  if: always()
@@ -0,0 +1,109 @@
1
+ name: Render and Deploy to GitHub Pages
2
+
3
+ # Triggered automatically on every push to main or master, or manually via
4
+ # the Actions tab (or when dispatched by menu.yml for a render action).
5
+ # Renders the specification and pushes the output to the gh-pages branch.
6
+ # docs/ is NEVER committed to main/master.
7
+
8
+ on:
9
+ push:
10
+ branches:
11
+ - main
12
+ - master
13
+ # Do not trigger on workflow file changes (.github/). Each workflow file
14
+ # is uploaded as a separate commit during repo creation, which would cause
15
+ # multiple competing runs in the same concurrency group. Real spec content
16
+ # changes (spec/, specs.json, package.json, etc.) still trigger a render.
17
+ # The initial render after repo creation is handled by an explicit dispatch.
18
+ # The trigger matrix is now:
19
+ #
20
+ # Event --> Triggers render?
21
+ #
22
+ # Push spec content (spec, specs.json, etc.) --> ✅ yes
23
+ # Push `.github/workflows/*.yml` (repo creation) --> ❌ no
24
+ # `workflow_dispatch` (GitHubUi "render" button, or menu.yml) --> ✅ yes
25
+ # PR into main/master --> ❌ not triggered (no `pull_request:` event)
26
+
27
+ paths-ignore:
28
+ - '.github/**'
29
+ workflow_dispatch:
30
+
31
+ # Only allow one deployment at a time; do not cancel in-progress runs so that
32
+ # a completed deploy is never replaced by a stale one.
33
+ # NOTE: do NOT use 'pages' as the group name — that conflicts with GitHub's own
34
+ # internal Pages deployment concurrency group and causes spurious cancellations.
35
+ concurrency:
36
+ group: render-and-deploy-${{ github.ref }}
37
+ cancel-in-progress: false
38
+
39
+ jobs:
40
+ # ── Build & Deploy ────────────────────────────────────────────────────────────
41
+ build-and-deploy:
42
+ runs-on: ubuntu-latest
43
+ # GitHub automatically generates a GITHUB_TOKEN for each workflow run.
44
+ # This ephemeral token is scoped only to the current repository and expires
45
+ # after the run completes. The 'permissions' block declares what this token
46
+ # is allowed to do — GitHub denies operations outside these scopes.
47
+ permissions:
48
+ contents: write # Required by peaceiris/actions-gh-pages to push to gh-pages
49
+ pages: write # Required to configure GitHub Pages source via API
50
+ steps:
51
+ - name: Checkout 🛎️
52
+ uses: actions/checkout@v4
53
+
54
+ - name: Set up Node.js
55
+ uses: actions/setup-node@v4
56
+ with:
57
+ node-version: '20'
58
+
59
+ - name: Install dependencies 🔧
60
+ run: npm install
61
+
62
+ # Collect external references — this also renders the specification and
63
+ # writes output to the path defined in specs.json (output_path, typically ./docs/).
64
+ - name: Collect external references and render
65
+ run: npm run collectExternalReferences
66
+
67
+ # Push the rendered ./docs output to the gh-pages branch.
68
+ # peaceiris/actions-gh-pages creates the branch on first run when it
69
+ # does not yet exist — no manual branch setup is required.
70
+ # Uses GITHUB_TOKEN (automatically injected by GitHub) with 'contents: write'
71
+ # permission to push to this repository.
72
+ - name: Deploy to GitHub Pages 🚀
73
+ uses: peaceiris/actions-gh-pages@v3
74
+ with:
75
+ github_token: ${{ secrets.GITHUB_TOKEN }}
76
+ publish_dir: ./docs
77
+ publish_branch: gh-pages
78
+
79
+ # Configure GitHub Pages to serve from the gh-pages branch (root /).
80
+ # This uses the GitHub REST API with GITHUB_TOKEN (permission: 'pages: write').
81
+ # It is idempotent — safe to run on every render (201=create, 409/422=exists, update).
82
+ # GITHUB_TOKEN is ephemeral (created per-run, expires at run end), scoped only to
83
+ # this repository, and never logged. GitHub accepts it because the token is issued
84
+ # by GitHub itself and this workflow declares 'pages: write' permission.
85
+ # continue-on-error: the API call may return a conflict on the very first run while
86
+ # GitHub is still processing the new gh-pages branch — this is non-fatal.
87
+ - name: Configure GitHub Pages source
88
+ continue-on-error: true
89
+ run: |
90
+ TOKEN="${{ secrets.GITHUB_TOKEN }}"
91
+ REPO="${{ github.repository }}"
92
+
93
+ HTTP_CODE=$(curl -s -o response.json -w "%{http_code}" \
94
+ -X POST \
95
+ -H "Authorization: token $TOKEN" \
96
+ -H "Accept: application/vnd.github+json" \
97
+ "https://api.github.com/repos/$REPO/pages" \
98
+ -d '{"source":{"branch":"gh-pages","path":"/"}}')
99
+
100
+ # 201 = created; 409/422 = already exists, update instead
101
+ if [ "$HTTP_CODE" -eq 409 ] || [ "$HTTP_CODE" -eq 422 ]; then
102
+ curl -s -o /dev/null \
103
+ -X PUT \
104
+ -H "Authorization: token $TOKEN" \
105
+ -H "Accept: application/vnd.github+json" \
106
+ "https://api.github.com/repos/$REPO/pages" \
107
+ -d '{"source":{"branch":"gh-pages","path":"/"}}'
108
+ fi
109
+ echo "GitHub Pages source set to gh-pages branch"
@@ -1,5 +1,10 @@
1
1
  name: Set GitHub Pages and Homepage
2
2
 
3
+ # One-time setup workflow: run this AFTER the first successful
4
+ # render-and-deploy run, which creates the gh-pages branch.
5
+ # It configures GitHub Pages to serve from the gh-pages branch (root /)
6
+ # and sets the repository homepage URL.
7
+
3
8
  on:
4
9
  workflow_dispatch:
5
10
 
@@ -7,93 +12,90 @@ jobs:
7
12
  configure-pages-and-homepage:
8
13
  runs-on: ubuntu-latest
9
14
  permissions:
10
- contents: read # To check branches
11
- pages: write # To configure Pages settings
15
+ contents: read
12
16
  steps:
13
- - name: Check if gh-pages branch exists
14
- id: check-branch
15
- run: |
16
- RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
17
- -H "Authorization: token ${{ secrets.MY_PAT }}" \
18
- -H "Accept: application/vnd.github+json" \
19
- https://api.github.com/repos/${{ github.repository }}/branches/gh-pages)
20
- if [ "$RESPONSE" -eq 200 ]; then
21
- echo "gh-pages branch exists"
22
- echo "BRANCH_EXISTS=true" >> $GITHUB_ENV
23
- elif [ "$RESPONSE" -eq 404 ]; then
24
- echo "Error: gh-pages branch does not exist in ${{ github.repository }}"
25
- exit 1
26
- else
27
- echo "Unexpected response code: $RESPONSE"
28
- exit 1
29
- fi
30
-
31
- - name: Set GitHub Pages to gh-pages
32
- if: env.BRANCH_EXISTS == 'true'
17
+ # Configure Pages to serve from the gh-pages branch (root /).
18
+ # POST creates the resource; a 409 means it already exists, so we PUT
19
+ # to update the source to the gh-pages branch.
20
+ - name: Enable GitHub Pages (gh-pages branch source)
33
21
  env:
34
22
  PAT: ${{ secrets.MY_PAT }}
35
23
  run: |
36
- RESPONSE=$(curl -s -L \
24
+ HTTP_CODE=$(curl -s -L \
37
25
  -X POST \
38
26
  -H "Authorization: token $PAT" \
39
27
  -H "Accept: application/vnd.github+json" \
40
28
  https://api.github.com/repos/${{ github.repository }}/pages \
41
29
  -d '{"source":{"branch":"gh-pages","path":"/"}}' \
42
- -w "%{http_code}" -o response.json)
43
- if [ "$RESPONSE" -eq 201 ] || [ "$RESPONSE" -eq 200 ]; then
44
- echo "GitHub Pages set to gh-pages branch for ${{ github.repository }}"
30
+ -w "%{http_code}" -o response.json | tail -n1)
31
+
32
+ if [ "$HTTP_CODE" -eq 201 ] || [ "$HTTP_CODE" -eq 200 ]; then
33
+ echo "GitHub Pages enabled with gh-pages branch as source"
34
+ elif [ "$HTTP_CODE" -eq 409 ]; then
35
+ echo "Pages already exists — updating source to gh-pages branch..."
36
+ HTTP_CODE2=$(curl -s -L \
37
+ -X PUT \
38
+ -H "Authorization: token $PAT" \
39
+ -H "Accept: application/vnd.github+json" \
40
+ https://api.github.com/repos/${{ github.repository }}/pages \
41
+ -d '{"source":{"branch":"gh-pages","path":"/"}}' \
42
+ -w "%{http_code}" -o response2.json | tail -n1)
43
+ if [ "$HTTP_CODE2" -eq 200 ] || [ "$HTTP_CODE2" -eq 204 ]; then
44
+ echo "GitHub Pages source updated to gh-pages branch"
45
+ else
46
+ echo "Failed to update Pages: $HTTP_CODE2"
47
+ cat response2.json
48
+ exit 1
49
+ fi
45
50
  else
46
- echo "Failed to set GitHub Pages: $RESPONSE"
51
+ echo "Failed to configure Pages: $HTTP_CODE"
47
52
  cat response.json
48
53
  exit 1
49
54
  fi
50
55
 
56
+ # Set the repository homepage field to the expected GitHub Pages URL so
57
+ # it appears prominently on the repository landing page.
51
58
  - name: Set repository homepage to GitHub Pages URL
52
- if: env.BRANCH_EXISTS == 'true'
53
59
  env:
54
60
  PAT: ${{ secrets.MY_PAT }}
55
61
  run: |
56
- # Construct the expected GitHub Pages URL
57
62
  REPO_NAME=$(echo "${{ github.repository }}" | cut -d'/' -f2)
58
63
  OWNER=$(echo "${{ github.repository }}" | cut -d'/' -f1)
59
64
  PAGES_URL="https://${OWNER}.github.io/${REPO_NAME}"
60
65
 
61
- # Set the homepage via API
62
- RESPONSE=$(curl -s -L \
66
+ HTTP_CODE=$(curl -s -L \
63
67
  -X PATCH \
64
68
  -H "Authorization: token $PAT" \
65
69
  -H "Accept: application/vnd.github+json" \
66
70
  https://api.github.com/repos/${{ github.repository }} \
67
71
  -d "{\"homepage\":\"${PAGES_URL}\"}" \
68
- -w "%{http_code}" -o response.json)
72
+ -w "%{http_code}" -o response.json | tail -n1)
69
73
 
70
- if [ "$RESPONSE" -eq 200 ]; then
71
- echo "Repository homepage set to $PAGES_URL for ${{ github.repository }}"
74
+ if [ "$HTTP_CODE" -eq 200 ]; then
75
+ echo "Repository homepage set to $PAGES_URL"
72
76
  else
73
- echo "Failed to set homepage: $RESPONSE"
77
+ echo "Failed to set homepage: $HTTP_CODE"
74
78
  cat response.json
75
79
  exit 1
76
80
  fi
77
81
 
82
+ # Verify the homepage was written correctly.
78
83
  - name: Verify homepage matches GitHub Pages URL
79
- if: env.BRANCH_EXISTS == 'true'
80
84
  env:
81
85
  PAT: ${{ secrets.MY_PAT }}
82
86
  run: |
83
- # Construct the expected GitHub Pages URL
84
87
  REPO_NAME=$(echo "${{ github.repository }}" | cut -d'/' -f2)
85
88
  OWNER=$(echo "${{ github.repository }}" | cut -d'/' -f1)
86
89
  EXPECTED_URL="https://${OWNER}.github.io/${REPO_NAME}"
87
90
 
88
- # Fetch the current homepage from the repo
89
91
  CURRENT_HOMEPAGE=$(curl -s -L \
90
92
  -H "Authorization: token $PAT" \
91
93
  -H "Accept: application/vnd.github+json" \
92
94
  https://api.github.com/repos/${{ github.repository }} | jq -r '.homepage')
93
95
 
94
96
  if [ "$CURRENT_HOMEPAGE" = "$EXPECTED_URL" ]; then
95
- echo "Verified: Homepage is set to GitHub Pages URL ($EXPECTED_URL)"
97
+ echo "Verified: homepage is $EXPECTED_URL"
96
98
  else
97
- echo "Error: Homepage ($CURRENT_HOMEPAGE) does not match expected GitHub Pages URL ($EXPECTED_URL)"
99
+ echo "Error: homepage ($CURRENT_HOMEPAGE) does not match $EXPECTED_URL"
98
100
  exit 1
99
101
  fi
@@ -1,11 +1,61 @@
1
+ #######################
2
+ #
3
+ # BEGIN SPEC-UP-T rules
4
+ #
5
+ #######################
6
+
7
+ # This file represents the recommended .gitignore for Spec-Up-T projects.
8
+ # If you only need to ignore something locally and don’t want to
9
+ # commit the rule, you can add the pattern to your repository’s
10
+ # `.git/info/exclude` file instead of modifying the shared .gitignore.
11
+ # Entries in `.git/info/exclude` stay completely local.
12
+
13
+ # Generated by render-and-deploy.yml — do not commit build output
14
+ docs/
15
+
16
+ # Dependencies
1
17
  node_modules/
18
+
19
+ # Logs
2
20
  *.log
3
- dist
21
+
22
+ # Editor / OS temporaries & backups
4
23
  *.bak
5
24
  *.tmp
6
- .DS_Store
7
- .env
8
- coverage
9
- build
25
+ .idea
26
+ .vscode/
27
+
28
+ # Environment / secrets
29
+ .env*
30
+
31
+ # Test coverage
32
+ coverage/
33
+
34
+ # Various caches & history
10
35
  .history/
11
- /.cache/
36
+ .cache/
37
+
38
+ # macOS
39
+ .DS_Store
40
+ .AppleDouble
41
+ .LSOverride
42
+ Icon\r # (the \r is intentional — it's a hidden carriage-return file)
43
+
44
+ # Windows
45
+ Thumbs.db
46
+ ehthumbs.db
47
+ Desktop.ini
48
+ $RECYCLE.BIN/
49
+ *.lnk # Windows shortcuts (optional, but very common noise)
50
+
51
+ # Linux / Ubuntu / general Unix-like
52
+ *~ # Editor backup files (Vim, Nano, etc.)
53
+ .*.swp # Vim swap files
54
+ .*.swo
55
+ .fuse_hidden* # Sometimes appears with fuse mounts
56
+
57
+ #######################
58
+ #
59
+ # END SPEC-UP-T rules
60
+ #
61
+ #######################
@@ -12,7 +12,9 @@ const gitIgnoreEntries = {
12
12
  '.env',
13
13
  'coverage',
14
14
  'build',
15
- '.history'
15
+ '.history',
16
+ // Generated by render-and-deploy.yml — do not commit build output
17
+ 'docs'
16
18
  ],
17
19
  };
18
20
 
@@ -3,6 +3,7 @@ const systemFiles = [
3
3
  '.env.example',
4
4
  'menu-wrapper.sh',
5
5
  '.github/workflows/menu.yml',
6
+ '.github/workflows/render-and-deploy.yml',
6
7
  '.github/workflows/set-gh-pages.yml',
7
8
  'assets/test.json',
8
9
  'assets/test.text',
@@ -29,6 +29,11 @@ function createVersionsIndex(outputPath) {
29
29
  // Get all directories in the destination directory
30
30
  const dirs = fs.readdirSync(versionsDir).filter(file => fs.statSync(path.join(versionsDir, file)).isDirectory());
31
31
 
32
+ // Load persisted labels written by freeze-spec-data.js.
33
+ // Falls back to an empty object when no labels file exists yet.
34
+ const labelsFile = path.join(versionsDir, 'labels.json');
35
+ const labels = fs.existsSync(labelsFile) ? fs.readJsonSync(labelsFile) : {};
36
+
32
37
  // Generate HTML content
33
38
  let htmlContent = `
34
39
  <!DOCTYPE html>
@@ -58,7 +63,9 @@ function createVersionsIndex(outputPath) {
58
63
  htmlContent += ` <li class="list-group-item">No versions available</li>\n`;
59
64
  } else {
60
65
  dirs.forEach(dir => {
61
- htmlContent += ` <li class="list-group-item"><a href="${dir}/">Version ${dir}</a></li>\n`;
66
+ // Use the stored label when available; fall back to the directory name
67
+ const linkText = labels[dir] || `Version ${dir}`;
68
+ htmlContent += ` <li class="list-group-item"><a href="${dir}/">${linkText}</a></li>\n`;
62
69
  });
63
70
  }
64
71