Add Python/Ruby code pieces and fix template loading performance
- Add Python code execution piece with subprocess-based runner - Add Ruby code execution piece with subprocess-based runner - Fix template loading: fetch individual templates from cloud for community edition - Add piece name aliasing for renamed pieces (piece-text-ai → piece-ai, etc.) - Add dev pieces caching to avoid disk reads on every request (60s TTL) - Add Python and Ruby logos to Django static files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,19 @@ import { pieceListUtils } from './utils'
|
||||
|
||||
export const pieceRepos = repoFactory(PieceMetadataEntity)
|
||||
|
||||
// Map of old/renamed piece names to their current names
|
||||
// This allows templates with old piece references to still work
|
||||
const PIECE_NAME_ALIASES: Record<string, string> = {
|
||||
'@activepieces/piece-text-ai': '@activepieces/piece-ai',
|
||||
'@activepieces/piece-utility-ai': '@activepieces/piece-ai',
|
||||
'@activepieces/piece-image-ai': '@activepieces/piece-ai',
|
||||
}
|
||||
|
||||
// Cache for dev pieces to avoid reading from disk on every request
|
||||
let devPiecesCache: PieceMetadataSchema[] | null = null
|
||||
let devPiecesCacheTime: number = 0
|
||||
const DEV_PIECES_CACHE_TTL_MS = 60000 // 1 minute cache
|
||||
|
||||
export const pieceMetadataService = (log: FastifyBaseLogger) => {
|
||||
return {
|
||||
async setup(): Promise<void> {
|
||||
@@ -89,13 +102,35 @@ export const pieceMetadataService = (log: FastifyBaseLogger) => {
|
||||
release: undefined,
|
||||
log,
|
||||
})
|
||||
const piece = originalPieces.find((piece) => {
|
||||
let piece = originalPieces.find((piece) => {
|
||||
const strictlyLessThan = (isNil(versionToSearch) || (
|
||||
semVer.compare(piece.version, versionToSearch.nextExcludedVersion) < 0
|
||||
&& semVer.compare(piece.version, versionToSearch.baseVersion) >= 0
|
||||
))
|
||||
return piece.name === name && strictlyLessThan
|
||||
})
|
||||
|
||||
// Fall back to latest version if specific version not found
|
||||
// This allows templates with old piece versions to still work
|
||||
if (isNil(piece) && !isNil(version)) {
|
||||
piece = originalPieces.find((p) => p.name === name)
|
||||
if (!isNil(piece)) {
|
||||
log.info(`Piece ${name} version ${version} not found, falling back to latest version ${piece.version}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Try piece name alias if piece still not found
|
||||
// This handles renamed pieces (e.g., piece-text-ai -> piece-ai)
|
||||
if (isNil(piece)) {
|
||||
const aliasedName = PIECE_NAME_ALIASES[name]
|
||||
if (!isNil(aliasedName)) {
|
||||
piece = originalPieces.find((p) => p.name === aliasedName)
|
||||
if (!isNil(piece)) {
|
||||
log.info(`Piece ${name} not found, using alias ${aliasedName} (version ${piece.version})`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isFiltered = !isNil(piece) && await enterpriseFilteringUtils.isFiltered({
|
||||
piece,
|
||||
projectId,
|
||||
@@ -287,10 +322,20 @@ const loadDevPiecesIfEnabled = async (log: FastifyBaseLogger): Promise<PieceMeta
|
||||
if (isNil(devPiecesConfig) || isEmpty(devPiecesConfig)) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Check if cache is still valid
|
||||
const now = Date.now()
|
||||
if (!isNil(devPiecesCache) && (now - devPiecesCacheTime) < DEV_PIECES_CACHE_TTL_MS) {
|
||||
log.debug(`Using cached dev pieces (${devPiecesCache.length} pieces, age: ${now - devPiecesCacheTime}ms)`)
|
||||
return devPiecesCache
|
||||
}
|
||||
|
||||
// Cache expired or doesn't exist, load from disk
|
||||
log.info('Loading dev pieces from disk (cache expired or empty)')
|
||||
const piecesNames = devPiecesConfig.split(',')
|
||||
const pieces = await filePiecesUtils(log).loadDistPiecesMetadata(piecesNames)
|
||||
|
||||
return pieces.map((p): PieceMetadataSchema => ({
|
||||
const result = pieces.map((p): PieceMetadataSchema => ({
|
||||
id: apId(),
|
||||
...p,
|
||||
projectUsage: 0,
|
||||
@@ -299,6 +344,13 @@ const loadDevPiecesIfEnabled = async (log: FastifyBaseLogger): Promise<PieceMeta
|
||||
created: new Date().toISOString(),
|
||||
updated: new Date().toISOString(),
|
||||
}))
|
||||
|
||||
// Update cache
|
||||
devPiecesCache = result
|
||||
devPiecesCacheTime = now
|
||||
log.info(`Cached ${result.length} dev pieces`)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const findOldestCreatedDate = async ({ name, platformId }: { name: string, platformId?: string }): Promise<string> => {
|
||||
|
||||
@@ -25,6 +25,29 @@ export const communityTemplates = {
|
||||
const templates = await response.json()
|
||||
return templates
|
||||
},
|
||||
getById: async (id: string): Promise<Template | null> => {
|
||||
const templateSource = system.get(AppSystemProp.TEMPLATES_SOURCE_URL)
|
||||
if (isNil(templateSource)) {
|
||||
return null
|
||||
}
|
||||
// Fetch the template by ID from the cloud templates endpoint
|
||||
const url = `${templateSource}/${id}`
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
if (!response.ok) {
|
||||
return null
|
||||
}
|
||||
return await response.json()
|
||||
}
|
||||
catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,14 @@ const edition = system.getEdition()
|
||||
|
||||
export const templateController: FastifyPluginAsyncTypebox = async (app) => {
|
||||
app.get('/:id', GetParams, async (request) => {
|
||||
// For community edition, try to fetch from cloud templates first
|
||||
if (edition !== ApEdition.CLOUD) {
|
||||
const cloudTemplate = await communityTemplates.getById(request.params.id)
|
||||
if (!isNil(cloudTemplate)) {
|
||||
return cloudTemplate
|
||||
}
|
||||
}
|
||||
// Fall back to local database
|
||||
return templateService().getOneOrThrow({ id: request.params.id })
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user