3434 const STATUS_DONE = 'Done';
3535 const STATUS_MAINT = 'Maintenance';
3636 const LRD_FIELD_NAME = 'Last Reviewed Date';
37+ const PUBLISHED_URL_FIELD_NAME = 'Published URL';
3738 // ----------------------------------
3839
3940 // Dates
@@ -77,12 +78,20 @@ jobs:
7778 const findDateFieldId = (name) =>
7879 fields.find(f => f.__typename === 'ProjectV2Field' && f.name === name && f.dataType === 'DATE')?.id || null;
7980
81+ const findTextFieldId = (name) => {
82+ const exact = fields.find(f => f.__typename === 'ProjectV2Field' && f.name === name && f.dataType === 'TEXT');
83+ if (exact) return exact.id;
84+ const ci = fields.find(f => f.__typename === 'ProjectV2Field' && (f.name?.toLowerCase?.() === name.toLowerCase()) && f.dataType === 'TEXT');
85+ return ci?.id || null;
86+ };
87+
8088 const statusField = fields.find(f => f.__typename === 'ProjectV2SingleSelectField' && f.name === STATUS_FIELD_NAME);
8189 const statusFieldId = statusField?.id || null;
8290 const doneId = statusField?.options?.find(o => o.name === STATUS_DONE)?.id || null;
8391 const maintId = statusField?.options?.find(o => o.name === STATUS_MAINT)?.id || null;
8492
8593 const lrdId = findDateFieldId(LRD_FIELD_NAME);
94+ const publishedUrlFieldId = findTextFieldId(PUBLISHED_URL_FIELD_NAME);
8695
8796 if (!statusFieldId || !doneId || !maintId || !lrdId) {
8897 throw new Error('Missing required project fields/options: Status/Done/Maintenance or Last Reviewed Date.');
@@ -94,6 +103,11 @@ jobs:
94103 n.__typename === 'ProjectV2ItemFieldDateValue' && n.field?.id === fieldId
95104 )?.date || null;
96105
106+ const getText = (item, fieldId) =>
107+ item.fieldValues.nodes.find(n =>
108+ n.__typename === 'ProjectV2ItemFieldTextValue' && n.field?.id === fieldId
109+ )?.text || null;
110+
97111 const getStatusName = (item) => {
98112 const n = item.fieldValues.nodes.find(n =>
99113 n.__typename === 'ProjectV2ItemFieldSingleSelectValue' && n.field?.id === statusFieldId
@@ -107,7 +121,7 @@ jobs:
107121 return;
108122 }
109123 const m = `
110- mutation($p:ID!,$i:ID!,$f:ID!,$o:Date !){
124+ mutation($p:ID!,$i:ID!,$f:ID!,$o:String !){
111125 updateProjectV2ItemFieldValue(input:{
112126 projectId:$p, itemId:$i, fieldId:$f, value:{ singleSelectOptionId:$o }
113127 }){
@@ -129,15 +143,22 @@ jobs:
129143 id
130144 content{
131145 __typename
132- ... on PullRequest { number repository{ name } }
146+ ... on PullRequest {
147+ number
148+ repository { name }
149+ }
133150 }
134- fieldValues(first:50 ){
151+ fieldValues(first:100 ){
135152 nodes{
136153 __typename
137154 ... on ProjectV2ItemFieldDateValue {
138155 field { ... on ProjectV2Field { id name } }
139156 date
140157 }
158+ ... on ProjectV2ItemFieldTextValue {
159+ field { ... on ProjectV2Field { id name } }
160+ text
161+ }
141162 ... on ProjectV2ItemFieldSingleSelectValue {
142163 field { ... on ProjectV2SingleSelectField { id name } }
143164 name
@@ -146,20 +167,35 @@ jobs:
146167 }
147168 }
148169 }
149- pageInfo{ hasNextPage endCursor }
170+ pageInfo {
171+ hasNextPage
172+ endCursor
173+ }
150174 }
151175 }
152176 }
153177 }`,
154178 { org: orgLogin, num: projectNumber, after: cursor }
155179 );
180+
156181 const page = r.organization.projectV2.items;
157182 for (const n of page.nodes) yield n;
158183 if (!page.pageInfo.hasNextPage) break;
159184 cursor = page.pageInfo.endCursor;
160185 }
161186 }
162187
188+ const lastTwoFromUrl = (url) => {
189+ if (!url) return '';
190+ try {
191+ const u = new URL(url);
192+ const segs = u.pathname.split('/').filter(Boolean);
193+ if (segs.length >= 2) return `${segs[segs.length - 2]}/${segs[segs.length - 1]}/`;
194+ if (segs.length === 1) return `${segs[0]}/`;
195+ return '';
196+ } catch { return ''; }
197+ };
198+
163199 // Movement counters & log
164200 let movedDoneToMaint = 0;
165201 let movedMaintToDone = 0;
@@ -173,12 +209,18 @@ jobs:
173209 const status = getStatusName(item);
174210 const lrd = getDate(item, lrdId);
175211 if (!status || !lrd) continue; // only move when LRD exists
212+
213+ const prNumber = item.content.number;
214+ const repoName = item.content.repository.name;
215+
216+ const publishedUrl = publishedUrlFieldId ? getText(item, publishedUrlFieldId) : null;
217+ const lastTwoSegments = lastTwoFromUrl(publishedUrl) || '(no-published-url)';
176218
177219 // Done -> Maintenance: LRD older/equal than 6 months ago
178220 if (status === STATUS_DONE && toDate(lrd) <= toDate(sixMonthsAgoISO)) {
179221 await setStatus(itemId, statusFieldId, maintId);
180222 movedDoneToMaint++;
181- const line = `[Cron] Move Done → Maintenance (LRD ${lrd} ≤ ${sixMonthsAgoISO})`;
223+ const line = `[Cron] Moved ${lastTwoSegments} → Maintenance (LRD ${lrd} ≤ ${sixMonthsAgoISO})`;
182224 console.log(line);
183225 moveLog.push(line);
184226 continue; // skip second rule for same item
@@ -188,7 +230,7 @@ jobs:
188230 if (status === STATUS_MAINT && toDate(lrd) > toDate(sixMonthsAgoISO)) {
189231 await setStatus(itemId, statusFieldId, doneId);
190232 movedMaintToDone++;
191- const line = `[Cron] Move Maintenance → Done (LRD ${lrd} > ${sixMonthsAgoISO})`;
233+ const line = `[Cron] Moved ${lastTwoSegments} → Done (LRD ${lrd} > ${sixMonthsAgoISO})`;
192234 console.log(line);
193235 moveLog.push(line);
194236 }
@@ -209,4 +251,4 @@ jobs:
209251 ])
210252 .addHeading('Details', 2)
211253 .addCodeBlock(moveLog.join('\n') || 'No moves', 'text')
212- .write();
254+ .write();
0 commit comments