Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci-doctor.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions .github/workflows/dev.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pkg/workflow/js/collect_ndjson_output.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 30 additions & 17 deletions pkg/workflow/js/collect_ndjson_output.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import type { SafeOutputItem, SafeOutputItems } from "./types/safe-outputs";
import type { SafeOutputConfigs } from "./types/safe-outputs-config";
import type {
SafeOutputConfigs,
SafeOutputConfig,
SpecificSafeOutputConfig,
CreateIssueConfig,
CreateDiscussionConfig,
AddCommentConfig,
CreatePullRequestConfig,
CreatePullRequestReviewCommentConfig,
CreateCodeScanningAlertConfig,
AddLabelsConfig,
UpdateIssueConfig,
PushToPullRequestBranchConfig,
UploadAssetConfig,
MissingToolConfig,
} from "./types/safe-outputs-config";

async function main() {
const fs = require("fs");
Expand Down Expand Up @@ -141,13 +156,14 @@ async function main() {
/**
* Gets the maximum allowed count for a given output type
* @param {string} itemType - The output item type
* @param {any} config - The safe-outputs configuration
* @param {SafeOutputConfigs} config - The safe-outputs configuration
* @returns {number} The maximum allowed count
*/
function getMaxAllowedForType(itemType: string, config: SafeOutputConfigs) {
function getMaxAllowedForType(itemType: string, config: SafeOutputConfigs): number {
// Check if max is explicitly specified in config
if (config && config[itemType] && typeof config[itemType] === "object" && config[itemType].max) {
return config[itemType].max;
const itemConfig = config?.[itemType];
if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) {
return itemConfig.max;
}

// Use default limits for plural-supported types
Expand Down Expand Up @@ -191,7 +207,6 @@ async function main() {
// U+0014 (DC4) — represented here as "\u0014"
// Escape control characters not allowed in JSON strings (U+0000 through U+001F)
// Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest.
/** @type {Record<number, string>} */
const _ctrl: Record<number, string> = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" };
repaired = repaired.replace(/[\u0000-\u001F]/g, ch => {
const c = ch.charCodeAt(0);
Expand Down Expand Up @@ -398,9 +413,9 @@ async function main() {
/**
* Attempts to parse JSON with repair fallback
* @param {string} jsonStr - The JSON string to parse
* @returns {Object|undefined} The parsed JSON object, or undefined if parsing fails
* @returns {any|undefined} The parsed JSON object, or undefined if parsing fails
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot use unknown

*/
function parseJsonWithRepair(jsonStr: string) {
function parseJsonWithRepair(jsonStr: string): any | undefined {
try {
// First, try normal JSON.parse
return JSON.parse(jsonStr);
Expand Down Expand Up @@ -444,11 +459,10 @@ async function main() {
core.info(`Raw output content length: ${outputContent.length}`);

// Parse the safe-outputs configuration
/** @type {any} */
let expectedOutputTypes = {};
let expectedOutputTypes: SafeOutputConfigs = {};
if (safeOutputsConfig) {
try {
expectedOutputTypes = JSON.parse(safeOutputsConfig);
expectedOutputTypes = JSON.parse(safeOutputsConfig) as SafeOutputConfigs;
core.info(`Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}`);
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
Expand All @@ -465,8 +479,7 @@ async function main() {
const line = lines[i].trim();
if (line === "") continue; // Skip empty lines
try {
/** @type {any} */
const item = parseJsonWithRepair(line);
const item = parseJsonWithRepair(line) as any;

// If item is undefined (failed to parse), add error and process next line
if (item === undefined) {
Expand Down Expand Up @@ -512,7 +525,7 @@ async function main() {
item.body = sanitizeContent(item.body);
// Sanitize labels if present
if (item.labels && Array.isArray(item.labels)) {
item.labels = item.labels.map(/** @param {any} label */ label => (typeof label === "string" ? sanitizeContent(label) : label));
item.labels = item.labels.map((label: any) => (typeof label === "string" ? sanitizeContent(label) : label));
}
break;

Expand Down Expand Up @@ -550,7 +563,7 @@ async function main() {
item.branch = sanitizeContent(item.branch);
// Sanitize labels if present
if (item.labels && Array.isArray(item.labels)) {
item.labels = item.labels.map(/** @param {any} label */ label => (typeof label === "string" ? sanitizeContent(label) : label));
item.labels = item.labels.map((label: any) => (typeof label === "string" ? sanitizeContent(label) : label));
}
break;

Expand All @@ -559,7 +572,7 @@ async function main() {
errors.push(`Line ${i + 1}: add_labels requires a 'labels' array field`);
continue;
}
if (item.labels.some(/** @param {any} label */ label => typeof label !== "string")) {
if (item.labels.some((label: any) => typeof label !== "string")) {
errors.push(`Line ${i + 1}: add_labels labels array must contain only strings`);
continue;
}
Expand All @@ -570,7 +583,7 @@ async function main() {
continue;
}
// Sanitize label strings
item.labels = item.labels.map(/** @param {any} label */ label => sanitizeContent(label));
item.labels = item.labels.map((label: any) => sanitizeContent(label));
break;

case "update-issue":
Expand Down
144 changes: 142 additions & 2 deletions pkg/workflow/js/types/safe-outputs-config.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,148 @@
// Base interface for all safe output configurations
interface SafeOutputConfig {
type: string;
max?: number;
}

type SafeOutputConfigs = Record<string, SafeOutputConfig>;
// === Specific Safe Output Configuration Interfaces ===

export { SafeOutputConfig, SafeOutputConfigs };
/**
* Configuration for creating GitHub issues
*/
interface CreateIssueConfig extends SafeOutputConfig {
"title-prefix"?: string;
labels?: string[];
max?: number;
"github-token"?: string;
}

/**
* Configuration for creating GitHub discussions
*/
interface CreateDiscussionConfig extends SafeOutputConfig {
"title-prefix"?: string;
"category-id"?: string;
max?: number;
"github-token"?: string;
}

/**
* Configuration for adding comments to issues or PRs
*/
interface AddCommentConfig extends SafeOutputConfig {
max?: number;
target?: string;
"github-token"?: string;
}

/**
* Configuration for creating pull requests
*/
interface CreatePullRequestConfig extends SafeOutputConfig {
"title-prefix"?: string;
labels?: string[];
draft?: boolean;
max?: number;
"if-no-changes"?: string;
"github-token"?: string;
}

/**
* Configuration for creating pull request review comments
*/
interface CreatePullRequestReviewCommentConfig extends SafeOutputConfig {
max?: number;
side?: string;
"github-token"?: string;
}

/**
* Configuration for creating code scanning alerts
*/
interface CreateCodeScanningAlertConfig extends SafeOutputConfig {
max?: number;
driver?: string;
"github-token"?: string;
}

/**
* Configuration for adding labels to issues or PRs
*/
interface AddLabelsConfig extends SafeOutputConfig {
allowed?: string[];
max?: number;
"github-token"?: string;
}

/**
* Configuration for updating issues
*/
interface UpdateIssueConfig extends SafeOutputConfig {
status?: boolean;
target?: string;
title?: boolean;
body?: boolean;
max?: number;
"github-token"?: string;
}

/**
* Configuration for pushing to pull request branches
*/
interface PushToPullRequestBranchConfig extends SafeOutputConfig {
target?: string;
"if-no-changes"?: string;
"github-token"?: string;
}

/**
* Configuration for uploading assets
*/
interface UploadAssetConfig extends SafeOutputConfig {
branch?: string;
"max-size"?: number;
"allowed-exts"?: string[];
"github-token"?: string;
}

/**
* Configuration for reporting missing tools
*/
interface MissingToolConfig extends SafeOutputConfig {
max?: number;
"github-token"?: string;
}

// Union type of all specific safe output configurations
type SpecificSafeOutputConfig =
| CreateIssueConfig
| CreateDiscussionConfig
| AddCommentConfig
| CreatePullRequestConfig
| CreatePullRequestReviewCommentConfig
| CreateCodeScanningAlertConfig
| AddLabelsConfig
| UpdateIssueConfig
| PushToPullRequestBranchConfig
| UploadAssetConfig
| MissingToolConfig;

type SafeOutputConfigs = Record<string, SafeOutputConfig | SpecificSafeOutputConfig>;

export {
SafeOutputConfig,
SafeOutputConfigs,
// Specific configuration types
CreateIssueConfig,
CreateDiscussionConfig,
AddCommentConfig,
CreatePullRequestConfig,
CreatePullRequestReviewCommentConfig,
CreateCodeScanningAlertConfig,
AddLabelsConfig,
UpdateIssueConfig,
PushToPullRequestBranchConfig,
UploadAssetConfig,
MissingToolConfig,
SpecificSafeOutputConfig,
};
Loading