Compare commits

..

1 Commits

Author SHA1 Message Date
Asuka Minato
c67d7c8e55 Add conditional comment creation for diff 2026-04-14 23:15:57 +09:00
11 changed files with 10 additions and 2174 deletions

View File

@@ -79,10 +79,11 @@ jobs:
const body = diff.trim()
? '### Pyrefly Diff\n<details>\n<summary>base → PR</summary>\n\n```diff\n' + diff + '\n```\n</details>'
: '### Pyrefly Diff\nNo changes detected.';
await github.rest.issues.createComment({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
if (diff.trim()) {
await github.rest.issues.createComment({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
}

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const entryFile = path.join(packageRoot, 'dist', 'cli.mjs')
if (!fs.existsSync(entryFile))
throw new Error(`Built CLI entry not found at ${entryFile}. Run "pnpm --filter @dify/cli build" first.`)
const result = spawnSync(
process.execPath,
[entryFile, ...process.argv.slice(2)],
{
cwd: process.cwd(),
env: process.env,
stdio: 'inherit',
},
)
if (result.error)
throw result.error
process.exit(result.status ?? 1)

View File

@@ -1,20 +0,0 @@
{
"name": "@dify/cli",
"private": true,
"version": "0.0.0-private",
"type": "module",
"bin": {
"dify-cli": "./bin/dify-cli.js"
},
"scripts": {
"build": "vp pack"
},
"dependencies": {
"typescript": "catalog:"
},
"devDependencies": {
"@types/node": "catalog:",
"vite": "catalog:",
"vite-plus": "catalog:"
}
}

View File

@@ -1,54 +0,0 @@
import process from 'node:process'
import { runMigrationCommand } from './no-unchecked-indexed-access/migrate'
import { runNormalizeCommand } from './no-unchecked-indexed-access/normalize'
import { runBatchMigrationCommand } from './no-unchecked-indexed-access/run'
type CommandHandler = (argv: string[]) => Promise<void>
const COMMANDS = new Map<string, CommandHandler>([
['migrate', runMigrationCommand],
['normalize', runNormalizeCommand],
['run', runBatchMigrationCommand],
])
function printUsage() {
console.log(`Usage:
dify-cli no-unchecked-indexed-access migrate [options]
dify-cli no-unchecked-indexed-access run [options]
dify-cli no-unchecked-indexed-access normalize`)
}
async function main() {
const [group, command, ...restArgs] = process.argv.slice(2)
if (!group || group === 'help' || group === '--help' || group === '-h') {
printUsage()
return
}
if (group !== 'no-unchecked-indexed-access') {
printUsage()
throw new Error(`Unknown command group: ${group}`)
}
if (!command || command === 'help' || command === '--help' || command === '-h') {
printUsage()
return
}
const handler = COMMANDS.get(command)
if (!handler) {
printUsage()
throw new Error(`Unknown command: ${command}`)
}
await handler(restArgs)
}
try {
await main()
}
catch (error) {
console.error(error instanceof Error ? error.message : error)
process.exitCode = 1
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +0,0 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'
import { normalizeMalformedAssertions } from './migrate'
const ROOT = process.cwd()
const EXTENSIONS = new Set(['.ts', '.tsx'])
async function collectFiles(directory: string): Promise<string[]> {
const entries = await fs.readdir(directory, { withFileTypes: true })
const files: string[] = []
for (const entry of entries) {
if (entry.name === 'node_modules' || entry.name === '.next')
continue
const absolutePath = path.join(directory, entry.name)
if (entry.isDirectory()) {
files.push(...await collectFiles(absolutePath))
continue
}
if (!EXTENSIONS.has(path.extname(entry.name)))
continue
files.push(absolutePath)
}
return files
}
async function main() {
const files = await collectFiles(ROOT)
let changedFileCount = 0
await Promise.all(files.map(async (fileName) => {
const currentText = await fs.readFile(fileName, 'utf8')
const nextText = normalizeMalformedAssertions(currentText)
if (nextText === currentText)
return
await fs.writeFile(fileName, nextText)
changedFileCount += 1
}))
console.log(`Normalized malformed assertion syntax in ${changedFileCount} file(s).`)
}
export async function runNormalizeCommand(_argv: string[]) {
await main()
}

View File

@@ -1,232 +0,0 @@
import { execFile } from 'node:child_process'
import path from 'node:path'
import process from 'node:process'
import { promisify } from 'node:util'
import { runMigration, SUPPORTED_DIAGNOSTIC_CODES } from './migrate'
const execFileAsync = promisify(execFile)
const DIAGNOSTIC_PATTERN = /^(.+?\.(?:ts|tsx))\((\d+),(\d+)\): error TS(\d+): (.+)$/
const DEFAULT_BATCH_SIZE = 100
const DEFAULT_BATCH_ITERATIONS = 5
const DEFAULT_MAX_ROUNDS = 20
type CliOptions = {
batchIterations: number
batchSize: number
maxRounds: number
project: string
verbose: boolean
}
type DiagnosticEntry = {
code: number
fileName: string
line: number
message: string
}
function parseArgs(argv: string[]): CliOptions {
const options: CliOptions = {
batchIterations: DEFAULT_BATCH_ITERATIONS,
batchSize: DEFAULT_BATCH_SIZE,
maxRounds: DEFAULT_MAX_ROUNDS,
project: 'tsconfig.json',
verbose: false,
}
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i]
if (arg === '--')
continue
if (arg === '--verbose') {
options.verbose = true
continue
}
if (arg === '--project') {
const value = argv[i + 1]
if (!value)
throw new Error('Missing value for --project')
options.project = value
i += 1
continue
}
if (arg === '--batch-size') {
const value = Number(argv[i + 1])
if (!Number.isInteger(value) || value <= 0)
throw new Error('Invalid value for --batch-size')
options.batchSize = value
i += 1
continue
}
if (arg === '--batch-iterations') {
const value = Number(argv[i + 1])
if (!Number.isInteger(value) || value <= 0)
throw new Error('Invalid value for --batch-iterations')
options.batchIterations = value
i += 1
continue
}
if (arg === '--max-rounds') {
const value = Number(argv[i + 1])
if (!Number.isInteger(value) || value <= 0)
throw new Error('Invalid value for --max-rounds')
options.maxRounds = value
i += 1
continue
}
throw new Error(`Unknown option: ${arg}`)
}
return options
}
async function runTypeCheck(project: string): Promise<{ diagnostics: DiagnosticEntry[], exitCode: number, rawOutput: string }> {
const projectDirectory = path.dirname(path.resolve(process.cwd(), project))
try {
const { stdout, stderr } = await execFileAsync('pnpm', ['exec', 'tsc', '--noEmit', '--pretty', 'false', '--incremental', 'false', '--project', project], {
cwd: projectDirectory,
env: {
...process.env,
NODE_OPTIONS: process.env.NODE_OPTIONS ?? '--max-old-space-size=8192',
},
maxBuffer: 1024 * 1024 * 32,
})
const rawOutput = `${stdout}${stderr}`.trim()
return {
diagnostics: parseDiagnostics(rawOutput, projectDirectory),
exitCode: 0,
rawOutput,
}
}
catch (error) {
const exitCode = typeof error === 'object' && error && 'code' in error && typeof error.code === 'number'
? error.code
: 1
const stdout = typeof error === 'object' && error && 'stdout' in error && typeof error.stdout === 'string'
? error.stdout
: ''
const stderr = typeof error === 'object' && error && 'stderr' in error && typeof error.stderr === 'string'
? error.stderr
: ''
const rawOutput = `${stdout}${stderr}`.trim()
return {
diagnostics: parseDiagnostics(rawOutput, projectDirectory),
exitCode,
rawOutput,
}
}
}
function parseDiagnostics(rawOutput: string, projectDirectory: string): DiagnosticEntry[] {
return rawOutput
.split('\n')
.map(line => line.trim())
.flatMap((line) => {
const match = line.match(DIAGNOSTIC_PATTERN)
if (!match)
return []
return [{
code: Number(match[4]),
fileName: path.resolve(projectDirectory, match[1]!),
line: Number(match[2]),
message: match[5] ?? '',
}]
})
}
function summarizeCodes(diagnostics: DiagnosticEntry[]): string {
const counts = new Map<number, number>()
for (const diagnostic of diagnostics)
counts.set(diagnostic.code, (counts.get(diagnostic.code) ?? 0) + 1)
return Array.from(counts.entries())
.sort((left, right) => right[1] - left[1])
.slice(0, 8)
.map(([code, count]) => `TS${code}:${count}`)
.join(', ')
}
function chunk<T>(items: T[], size: number): T[][] {
const batches: T[][] = []
for (let i = 0; i < items.length; i += size)
batches.push(items.slice(i, i + size))
return batches
}
async function runBatchMigration(options: CliOptions) {
for (let round = 1; round <= options.maxRounds; round += 1) {
const { diagnostics, exitCode, rawOutput } = await runTypeCheck(options.project)
if (exitCode === 0) {
console.log(`Type check passed after ${round - 1} migration round(s).`)
return
}
const supportedDiagnostics = diagnostics.filter(diagnostic => SUPPORTED_DIAGNOSTIC_CODES.has(diagnostic.code))
const unsupportedDiagnostics = diagnostics.filter(diagnostic => !SUPPORTED_DIAGNOSTIC_CODES.has(diagnostic.code))
const supportedFiles = Array.from(new Set(supportedDiagnostics.map(diagnostic => diagnostic.fileName)))
console.log(`Round ${round}: ${diagnostics.length} diagnostic(s). ${summarizeCodes(diagnostics)}`)
if (options.verbose) {
for (const diagnostic of diagnostics.slice(0, 40))
console.log(`${path.relative(process.cwd(), diagnostic.fileName)}:${diagnostic.line} TS${diagnostic.code} ${diagnostic.message}`)
}
if (supportedFiles.length === 0) {
console.error('No supported diagnostics remain to migrate.')
if (unsupportedDiagnostics.length > 0) {
console.error('Remaining unsupported diagnostics:')
for (const diagnostic of unsupportedDiagnostics.slice(0, 40))
console.error(`${path.relative(process.cwd(), diagnostic.fileName)}:${diagnostic.line} TS${diagnostic.code} ${diagnostic.message}`)
}
if (rawOutput)
process.stderr.write(`${rawOutput}\n`)
process.exitCode = 1
return
}
let roundEdits = 0
const batches = chunk(supportedFiles, options.batchSize)
for (const [index, batch] of batches.entries()) {
console.log(` Batch ${index + 1}/${batches.length}: ${batch.length} file(s)`)
const result = await runMigration({
files: batch,
maxIterations: options.batchIterations,
project: options.project,
verbose: options.verbose,
write: true,
})
roundEdits += result.totalEdits
}
if (roundEdits === 0) {
console.error('Migration script made no edits in this round; stopping to avoid an infinite loop.')
process.exitCode = 1
return
}
}
console.error(`Reached --max-rounds=${options.maxRounds} before type check passed.`)
process.exitCode = 1
}
export async function runBatchMigrationCommand(argv: string[]) {
await runBatchMigration(parseArgs(argv))
}

View File

@@ -1,17 +0,0 @@
import { defineConfig } from 'vite-plus'
export default defineConfig({
pack: {
clean: true,
deps: {
neverBundle: ['typescript'],
},
entry: ['src/cli.ts'],
format: ['esm'],
outDir: 'dist',
platform: 'node',
sourcemap: true,
target: 'node22',
treeshake: true,
},
})

19
pnpm-lock.yaml generated
View File

@@ -599,22 +599,6 @@ importers:
specifier: 'catalog:'
version: 0.1.16(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.16(@types/node@25.6.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(happy-dom@20.9.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)
packages/cli:
dependencies:
typescript:
specifier: 'catalog:'
version: 6.0.2
devDependencies:
'@types/node':
specifier: 'catalog:'
version: 25.6.0
vite:
specifier: npm:@voidzero-dev/vite-plus-core@0.1.16
version: '@voidzero-dev/vite-plus-core@0.1.16(@types/node@25.6.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)'
vite-plus:
specifier: 'catalog:'
version: 0.1.16(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.16(@types/node@25.6.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(happy-dom@20.9.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)
packages/iconify-collections:
devDependencies:
iconify-import-svg:
@@ -969,9 +953,6 @@ importers:
'@chromatic-com/storybook':
specifier: 'catalog:'
version: 5.1.2(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))
'@dify/cli':
specifier: workspace:*
version: link:../packages/cli
'@dify/iconify-collections':
specifier: workspace:*
version: link:../packages/iconify-collections

View File

@@ -17,8 +17,9 @@ const config: KnipConfig = {
],
ignoreDependencies: [
'@iconify-json/*',
'@storybook/addon-onboarding',
'@dify/cli',
],
/// keep-sorted
rules: {

View File

@@ -159,7 +159,6 @@
"devDependencies": {
"@antfu/eslint-config": "catalog:",
"@chromatic-com/storybook": "catalog:",
"@dify/cli": "workspace:*",
"@dify/iconify-collections": "workspace:*",
"@egoist/tailwindcss-icons": "catalog:",
"@eslint-react/eslint-plugin": "catalog:",