Skip to content

Latest Dev Errors Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings. #155

Latest Dev Errors Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

Latest Dev Errors Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings. #155

name: "Roadmap: Add to Project"
on:
issues:
types: [opened, reopened, transferred, labeled, unlabeled]
workflow_dispatch:
inputs:
issue_number:
description: "Issue number to resync"
required: true
jobs:
add-to-project:
name: Sync roadmap fields
runs-on: ubuntu-latest
environment: roadmap
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.issue != null && contains(github.event.issue.labels.*.name, 'roadmap'))
permissions:
contents: read
steps:
- name: Create GitHub App token
id: app_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.ROADMAP_BOT_APP_ID || secrets.ROADMAP_BOT_APP_ID }}
private-key: ${{ secrets.ROADMAP_BOT_PRIVATE_KEY }}
- name: Sync roadmap labels to project fields
uses: actions/github-script@v7
env:
PROJECT_ORG: happier-dev
PROJECT_NUMBER: "1"
PROJECT_PRIORITY_FIELD: Priority
PROJECT_RELEASE_STAGE_FIELD: Release stage
PROJECT_STATUS_FIELD: Status
with:
github-token: ${{ steps.app_token.outputs.token }}
script: |
const { owner, repo } = context.repo;
let issue = context.payload.issue ?? null;
if (!issue && context.eventName === 'workflow_dispatch') {
const raw = String(context.payload.inputs?.issue_number ?? '').trim();
const issueNumber = Number.parseInt(raw, 10);
if (!Number.isFinite(issueNumber) || issueNumber <= 0) {
throw new Error(`Invalid workflow_dispatch input: issue_number='${raw}'`);
}
const res = await github.rest.issues.get({ owner, repo, issue_number: issueNumber });
issue = res?.data ?? null;
}
const contentNodeId = issue?.node_id ?? null;
if (!contentNodeId) {
core.info('Missing issue/PR node id in event payload; skipping.');
return;
}
const labels = (issue?.labels ?? [])
.map((l) => (typeof l === 'string' ? l : String(l?.name ?? '')))
.map((n) => n.trim())
.filter(Boolean);
const priorityLabel = labels.find((name) => name.toLowerCase().startsWith('priority:'));
const stageLabel = labels.find((name) => name.toLowerCase().startsWith('stage:'));
const priorityOptionName = priorityLabel
? String(priorityLabel.split(':')[1] ?? '').trim().toUpperCase()
: null;
const stageKey = stageLabel ? String(stageLabel.split(':')[1] ?? '').trim().toLowerCase() : null;
const stageOptionName =
stageKey === 'ga'
? 'GA'
: stageKey === 'beta'
? 'Beta'
: stageKey === 'experimental'
? 'Experimental'
: stageKey === 'not-shipped'
? 'Not shipped'
: null;
if (!priorityOptionName && !stageOptionName) {
core.info('No priority:* or stage:* labels found; skipping field sync.');
return;
}
const org = String(process.env.PROJECT_ORG ?? '').trim();
const number = Number.parseInt(String(process.env.PROJECT_NUMBER ?? '0'), 10);
if (!org || !Number.isFinite(number) || number <= 0) {
throw new Error('Invalid project org/number configuration.');
}
const priorityFieldName = String(process.env.PROJECT_PRIORITY_FIELD ?? 'Priority');
const stageFieldName = String(process.env.PROJECT_RELEASE_STAGE_FIELD ?? 'Release stage');
const project = await github.graphql(
`
query ProjectFields($org: String!, $number: Int!) {
organization(login: $org) {
projectV2(number: $number) {
id
fields(first: 100) {
nodes {
__typename
... on ProjectV2SingleSelectField {
id
name
options { id name }
}
}
}
}
}
}
`,
{ org, number },
);
const projectV2 = project?.organization?.projectV2;
if (!projectV2?.id) throw new Error('Failed to resolve project id.');
const singleSelectFields = (projectV2.fields?.nodes ?? []).filter((n) => n?.__typename === 'ProjectV2SingleSelectField');
const findField = (name) => singleSelectFields.find((f) => String(f?.name ?? '').trim() === name);
const priorityField = findField(priorityFieldName);
const stageField = findField(stageFieldName);
async function findProjectItemIdForContent() {
let cursor = null;
// Reasonable cap: curated roadmap project shouldn't be massive; still paginate to be safe.
for (let page = 0; page < 20; page++) {
const res = await github.graphql(
`
query ProjectItems($org: String!, $number: Int!, $after: String) {
organization(login: $org) {
projectV2(number: $number) {
items(first: 50, after: $after) {
nodes {
id
content {
__typename
... on Issue { id }
... on PullRequest { id }
}
}
pageInfo { hasNextPage endCursor }
}
}
}
}
`,
{ org, number, after: cursor },
);
const items = res?.organization?.projectV2?.items;
const nodes = Array.isArray(items?.nodes) ? items.nodes : [];
const match = nodes.find((n) => String(n?.content?.id ?? '') === String(contentNodeId));
if (match?.id) return String(match.id);
if (!items?.pageInfo?.hasNextPage) return null;
cursor = items.pageInfo.endCursor ?? null;
if (!cursor) return null;
}
return null;
}
let itemId = null;
for (let attempt = 1; attempt <= 10; attempt++) {
itemId = await findProjectItemIdForContent();
if (itemId) break;
core.info(`Project item not found yet (attempt ${attempt}/10); waiting...`);
await new Promise((r) => setTimeout(r, 3000));
}
if (!itemId) {
core.info('Project item not found; ensure Project auto-add is enabled for label:roadmap.');
return;
}
async function setSingleSelect(field, optionName) {
if (!field?.id) return false;
const options = Array.isArray(field.options) ? field.options : [];
const option = options.find((o) => String(o?.name ?? '').trim() === optionName);
if (!option?.id) return false;
await github.graphql(
`
mutation SetField($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId
itemId: $itemId
fieldId: $fieldId
value: { singleSelectOptionId: $optionId }
}) { projectV2Item { id } }
}
`,
{ projectId: projectV2.id, itemId, fieldId: field.id, optionId: option.id },
);
return true;
}
if (priorityOptionName) {
const ok = await setSingleSelect(priorityField, priorityOptionName);
core.info(ok ? `Set ${priorityFieldName}=${priorityOptionName}` : `Skipped ${priorityFieldName} (field/option not found)`);
}
if (stageOptionName) {
const ok = await setSingleSelect(stageField, stageOptionName);
core.info(ok ? `Set ${stageFieldName}=${stageOptionName}` : `Skipped ${stageFieldName} (field/option not found)`);
}