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:
@@ -14,6 +14,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
apt-get install -y --no-install-recommends \
|
||||
openssh-client \
|
||||
python3 \
|
||||
python3-pip \
|
||||
ruby \
|
||||
g++ \
|
||||
build-essential \
|
||||
git \
|
||||
@@ -69,17 +71,21 @@ RUN --mount=type=cache,target=/root/.bun/install/cache \
|
||||
# Copy source code after dependency installation
|
||||
COPY . .
|
||||
|
||||
# Build all projects including the SmoothSchedule piece
|
||||
RUN npx nx run-many --target=build --projects=react-ui,server-api,pieces-smoothschedule --configuration production --parallel=2 --skip-nx-cache
|
||||
# Build all projects including custom pieces
|
||||
RUN npx nx run-many --target=build --projects=react-ui,server-api,pieces-smoothschedule,pieces-python-code,pieces-ruby-code --configuration production --parallel=2 --skip-nx-cache --verbose
|
||||
|
||||
# Install production dependencies only for the backend API
|
||||
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
||||
cd dist/packages/server/api && \
|
||||
bun install --production --frozen-lockfile
|
||||
|
||||
# Install dependencies for the SmoothSchedule piece
|
||||
# Install dependencies for custom pieces
|
||||
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
||||
cd dist/packages/pieces/community/smoothschedule && \
|
||||
bun install --production && \
|
||||
cd ../python-code && \
|
||||
bun install --production && \
|
||||
cd ../ruby-code && \
|
||||
bun install --production
|
||||
|
||||
### STAGE 2: Run ###
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@activepieces/piece-python-code",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "pieces-python-code",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/pieces/community/python-code/src",
|
||||
"projectType": "library",
|
||||
"release": {
|
||||
"version": {
|
||||
"currentVersionResolver": "git-tag",
|
||||
"preserveLocalDependencyProtocols": false,
|
||||
"manifestRootsToUpdate": [
|
||||
"dist/{projectRoot}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"options": {
|
||||
"outputPath": "dist/packages/pieces/community/python-code",
|
||||
"tsConfig": "packages/pieces/community/python-code/tsconfig.lib.json",
|
||||
"packageJson": "packages/pieces/community/python-code/package.json",
|
||||
"main": "packages/pieces/community/python-code/src/index.ts",
|
||||
"assets": [
|
||||
"packages/pieces/community/python-code/*.md"
|
||||
],
|
||||
"buildableProjectDepsInPackageJsonType": "dependencies",
|
||||
"updateBuildableProjectDepsInPackageJson": true
|
||||
},
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
"prebuild"
|
||||
]
|
||||
},
|
||||
"nx-release-publish": {
|
||||
"options": {
|
||||
"packageRoot": "dist/{projectRoot}"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": [
|
||||
"{options.outputFile}"
|
||||
]
|
||||
},
|
||||
"prebuild": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "packages/pieces/community/python-code",
|
||||
"command": "bun install --no-save --silent"
|
||||
},
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
|
||||
import { PieceCategory } from '@activepieces/shared';
|
||||
import { runPythonCode } from './lib/run-python-code';
|
||||
|
||||
// Python logo - hosted on Django backend
|
||||
const PYTHON_LOGO = 'http://lvh.me:8000/static/images/python-logo.svg';
|
||||
|
||||
export const pythonCode = createPiece({
|
||||
displayName: 'Python Code',
|
||||
description: 'Execute Python code in your automations',
|
||||
auth: PieceAuth.None(),
|
||||
minimumSupportedRelease: '0.36.1',
|
||||
logoUrl: PYTHON_LOGO,
|
||||
categories: [PieceCategory.CORE, PieceCategory.DEVELOPER_TOOLS],
|
||||
authors: ['smoothschedule'],
|
||||
actions: [runPythonCode],
|
||||
triggers: [],
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export const runPythonCode = createAction({
|
||||
name: 'run_python_code',
|
||||
displayName: 'Run Python Code',
|
||||
description: 'Execute Python code and return the output. Use print() to output results.',
|
||||
props: {
|
||||
code: Property.LongText({
|
||||
displayName: 'Python Code',
|
||||
description: 'The Python code to execute. Use print() to output results that will be captured.',
|
||||
required: true,
|
||||
defaultValue: `# Example: Process input data
|
||||
import json
|
||||
|
||||
# Access inputs via the 'inputs' variable (parsed from JSON)
|
||||
# Example: name = inputs.get('name', 'World')
|
||||
|
||||
# Your code here
|
||||
result = "Hello from Python!"
|
||||
|
||||
# Print your output (will be captured as the action result)
|
||||
print(result)`,
|
||||
}),
|
||||
inputs: Property.Object({
|
||||
displayName: 'Inputs',
|
||||
description: 'Input data to pass to the Python code. Available as the `inputs` variable (dict).',
|
||||
required: false,
|
||||
defaultValue: {},
|
||||
}),
|
||||
timeout: Property.Number({
|
||||
displayName: 'Timeout (seconds)',
|
||||
description: 'Maximum execution time in seconds',
|
||||
required: false,
|
||||
defaultValue: 30,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { code, inputs, timeout } = context.propsValue;
|
||||
const timeoutMs = (timeout || 30) * 1000;
|
||||
|
||||
// Create a temporary file for the Python code
|
||||
const tmpDir = os.tmpdir();
|
||||
const scriptPath = path.join(tmpDir, `ap_python_${Date.now()}.py`);
|
||||
const inputPath = path.join(tmpDir, `ap_python_input_${Date.now()}.json`);
|
||||
|
||||
try {
|
||||
// Write inputs to a JSON file
|
||||
await fs.promises.writeFile(inputPath, JSON.stringify(inputs || {}));
|
||||
|
||||
// Wrap the user code to load inputs
|
||||
const wrappedCode = `
|
||||
import json
|
||||
import sys
|
||||
|
||||
# Load inputs from JSON file
|
||||
with open('${inputPath.replace(/\\/g, '\\\\')}', 'r') as f:
|
||||
inputs = json.load(f)
|
||||
|
||||
# User code starts here
|
||||
${code}
|
||||
`;
|
||||
|
||||
// Write the script to a temp file
|
||||
await fs.promises.writeFile(scriptPath, wrappedCode);
|
||||
|
||||
// Execute Python
|
||||
const { stdout, stderr } = await execAsync(`python3 "${scriptPath}"`, {
|
||||
timeout: timeoutMs,
|
||||
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
||||
});
|
||||
|
||||
// Clean up temp files
|
||||
await fs.promises.unlink(scriptPath).catch(() => {});
|
||||
await fs.promises.unlink(inputPath).catch(() => {});
|
||||
|
||||
// Try to parse output as JSON, otherwise return as string
|
||||
const output = stdout.trim();
|
||||
let result: unknown;
|
||||
try {
|
||||
result = JSON.parse(output);
|
||||
} catch {
|
||||
result = output;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: result,
|
||||
stdout: stdout,
|
||||
stderr: stderr || null,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
// Clean up temp files on error
|
||||
await fs.promises.unlink(scriptPath).catch(() => {});
|
||||
await fs.promises.unlink(inputPath).catch(() => {});
|
||||
|
||||
const execError = error as { stderr?: string; message?: string; killed?: boolean };
|
||||
|
||||
if (execError.killed) {
|
||||
throw new Error(`Python script timed out after ${timeout} seconds`);
|
||||
}
|
||||
|
||||
throw new Error(`Python execution failed: ${execError.stderr || execError.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@activepieces/piece-ruby-code",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "pieces-ruby-code",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/pieces/community/ruby-code/src",
|
||||
"projectType": "library",
|
||||
"release": {
|
||||
"version": {
|
||||
"currentVersionResolver": "git-tag",
|
||||
"preserveLocalDependencyProtocols": false,
|
||||
"manifestRootsToUpdate": [
|
||||
"dist/{projectRoot}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"options": {
|
||||
"outputPath": "dist/packages/pieces/community/ruby-code",
|
||||
"tsConfig": "packages/pieces/community/ruby-code/tsconfig.lib.json",
|
||||
"packageJson": "packages/pieces/community/ruby-code/package.json",
|
||||
"main": "packages/pieces/community/ruby-code/src/index.ts",
|
||||
"assets": [
|
||||
"packages/pieces/community/ruby-code/*.md"
|
||||
],
|
||||
"buildableProjectDepsInPackageJsonType": "dependencies",
|
||||
"updateBuildableProjectDepsInPackageJson": true
|
||||
},
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
"prebuild"
|
||||
]
|
||||
},
|
||||
"nx-release-publish": {
|
||||
"options": {
|
||||
"packageRoot": "dist/{projectRoot}"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": [
|
||||
"{options.outputFile}"
|
||||
]
|
||||
},
|
||||
"prebuild": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "packages/pieces/community/ruby-code",
|
||||
"command": "bun install --no-save --silent"
|
||||
},
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
|
||||
import { PieceCategory } from '@activepieces/shared';
|
||||
import { runRubyCode } from './lib/run-ruby-code';
|
||||
|
||||
// Ruby logo - hosted on Django backend
|
||||
const RUBY_LOGO = 'http://lvh.me:8000/static/images/ruby-logo.svg';
|
||||
|
||||
export const rubyCode = createPiece({
|
||||
displayName: 'Ruby Code',
|
||||
description: 'Execute Ruby code in your automations',
|
||||
auth: PieceAuth.None(),
|
||||
minimumSupportedRelease: '0.36.1',
|
||||
logoUrl: RUBY_LOGO,
|
||||
categories: [PieceCategory.CORE, PieceCategory.DEVELOPER_TOOLS],
|
||||
authors: ['smoothschedule'],
|
||||
actions: [runRubyCode],
|
||||
triggers: [],
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export const runRubyCode = createAction({
|
||||
name: 'run_ruby_code',
|
||||
displayName: 'Run Ruby Code',
|
||||
description: 'Execute Ruby code and return the output. Use puts or print to output results.',
|
||||
props: {
|
||||
code: Property.LongText({
|
||||
displayName: 'Ruby Code',
|
||||
description: 'The Ruby code to execute. Use puts or print to output results that will be captured.',
|
||||
required: true,
|
||||
defaultValue: `# Example: Process input data
|
||||
require 'json'
|
||||
|
||||
# Access inputs via the 'inputs' variable (parsed from JSON)
|
||||
# Example: name = inputs['name'] || 'World'
|
||||
|
||||
# Your code here
|
||||
result = "Hello from Ruby!"
|
||||
|
||||
# Print your output (will be captured as the action result)
|
||||
puts result`,
|
||||
}),
|
||||
inputs: Property.Object({
|
||||
displayName: 'Inputs',
|
||||
description: 'Input data to pass to the Ruby code. Available as the `inputs` variable (Hash).',
|
||||
required: false,
|
||||
defaultValue: {},
|
||||
}),
|
||||
timeout: Property.Number({
|
||||
displayName: 'Timeout (seconds)',
|
||||
description: 'Maximum execution time in seconds',
|
||||
required: false,
|
||||
defaultValue: 30,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { code, inputs, timeout } = context.propsValue;
|
||||
const timeoutMs = (timeout || 30) * 1000;
|
||||
|
||||
// Create a temporary file for the Ruby code
|
||||
const tmpDir = os.tmpdir();
|
||||
const scriptPath = path.join(tmpDir, `ap_ruby_${Date.now()}.rb`);
|
||||
const inputPath = path.join(tmpDir, `ap_ruby_input_${Date.now()}.json`);
|
||||
|
||||
try {
|
||||
// Write inputs to a JSON file
|
||||
await fs.promises.writeFile(inputPath, JSON.stringify(inputs || {}));
|
||||
|
||||
// Escape the input path for Ruby
|
||||
const escapedInputPath = inputPath.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
||||
|
||||
// Wrap the user code to load inputs
|
||||
const wrappedCode = `
|
||||
require 'json'
|
||||
|
||||
# Load inputs from JSON file
|
||||
inputs = JSON.parse(File.read('${escapedInputPath}'))
|
||||
|
||||
# User code starts here
|
||||
${code}
|
||||
`;
|
||||
|
||||
// Write the script to a temp file
|
||||
await fs.promises.writeFile(scriptPath, wrappedCode);
|
||||
|
||||
// Execute Ruby
|
||||
const { stdout, stderr } = await execAsync(`ruby "${scriptPath}"`, {
|
||||
timeout: timeoutMs,
|
||||
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
||||
});
|
||||
|
||||
// Clean up temp files
|
||||
await fs.promises.unlink(scriptPath).catch(() => {});
|
||||
await fs.promises.unlink(inputPath).catch(() => {});
|
||||
|
||||
// Try to parse output as JSON, otherwise return as string
|
||||
const output = stdout.trim();
|
||||
let result: unknown;
|
||||
try {
|
||||
result = JSON.parse(output);
|
||||
} catch {
|
||||
result = output;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: result,
|
||||
stdout: stdout,
|
||||
stderr: stderr || null,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
// Clean up temp files on error
|
||||
await fs.promises.unlink(scriptPath).catch(() => {});
|
||||
await fs.promises.unlink(inputPath).catch(() => {});
|
||||
|
||||
const execError = error as { stderr?: string; message?: string; killed?: boolean };
|
||||
|
||||
if (execError.killed) {
|
||||
throw new Error(`Ruby script timed out after ${timeout} seconds`);
|
||||
}
|
||||
|
||||
throw new Error(`Ruby execution failed: ${execError.stderr || execError.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -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 })
|
||||
})
|
||||
|
||||
|
||||
@@ -31,9 +31,18 @@ AP_OPENAI_API_KEY=sk-proj-2y1XBAloHQ4cIVt2DIqN7sN11WB3TMM0GOMTy-1svntvCMz2dOGJAa
|
||||
AP_EXECUTION_MODE=UNSANDBOXED
|
||||
AP_TELEMETRY_ENABLED=false
|
||||
|
||||
# Pieces Configuration
|
||||
# CLOUD_AND_DB: Fetch pieces from cloud registry and local database
|
||||
AP_PIECES_SOURCE=CLOUD_AND_DB
|
||||
# OFFICIAL_AUTO: Automatically sync official pieces metadata from cloud
|
||||
AP_PIECES_SYNC_MODE=OFFICIAL_AUTO
|
||||
|
||||
# Embedding
|
||||
AP_EMBEDDING_ENABLED=true
|
||||
|
||||
# Development Pieces (comma-separated list of piece names to load from dist/packages/pieces)
|
||||
# The build watcher may fail without nx, but the piece should still load from pre-built dist
|
||||
AP_DEV_PIECES=smoothschedule
|
||||
AP_DEV_PIECES=smoothschedule,python-code,ruby-code
|
||||
|
||||
# Templates Source URL - fetch official Activepieces templates from cloud
|
||||
AP_TEMPLATES_SOURCE_URL=https://cloud.activepieces.com/api/v1/templates
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><linearGradient id="python-original-a" gradientUnits="userSpaceOnUse" x1="70.252" y1="1237.476" x2="170.659" y2="1151.089" gradientTransform="matrix(.563 0 0 -.568 -29.215 707.817)"><stop offset="0" stop-color="#5A9FD4"/><stop offset="1" stop-color="#306998"/></linearGradient><linearGradient id="python-original-b" gradientUnits="userSpaceOnUse" x1="209.474" y1="1098.811" x2="173.62" y2="1149.537" gradientTransform="matrix(.563 0 0 -.568 -29.215 707.817)"><stop offset="0" stop-color="#FFD43B"/><stop offset="1" stop-color="#FFE873"/></linearGradient><path fill="url(#python-original-a)" d="M63.391 1.988c-4.222.02-8.252.379-11.8 1.007-10.45 1.846-12.346 5.71-12.346 12.837v9.411h24.693v3.137H29.977c-7.176 0-13.46 4.313-15.426 12.521-2.268 9.405-2.368 15.275 0 25.096 1.755 7.311 5.947 12.519 13.124 12.519h8.491V67.234c0-8.151 7.051-15.34 15.426-15.34h24.665c6.866 0 12.346-5.654 12.346-12.548V15.833c0-6.693-5.646-11.72-12.346-12.837-4.244-.706-8.645-1.027-12.866-1.008zM50.037 9.557c2.55 0 4.634 2.117 4.634 4.721 0 2.593-2.083 4.69-4.634 4.69-2.56 0-4.633-2.097-4.633-4.69-.001-2.604 2.073-4.721 4.633-4.721z" transform="translate(0 10.26)"/><path fill="url(#python-original-b)" d="M91.682 28.38v10.966c0 8.5-7.208 15.655-15.426 15.655H51.591c-6.756 0-12.346 5.783-12.346 12.549v23.515c0 6.691 5.818 10.628 12.346 12.547 7.816 2.297 15.312 2.713 24.665 0 6.216-1.801 12.346-5.423 12.346-12.547v-9.412H63.938v-3.138h37.012c7.176 0 9.852-5.005 12.348-12.519 2.578-7.735 2.467-15.174 0-25.096-1.774-7.145-5.161-12.521-12.348-12.521h-9.268zM77.809 87.927c2.561 0 4.634 2.097 4.634 4.692 0 2.602-2.074 4.719-4.634 4.719-2.55 0-4.633-2.117-4.633-4.719 0-2.595 2.083-4.692 4.633-4.692z" transform="translate(0 10.26)"/><radialGradient id="python-original-c" cx="1825.678" cy="444.45" r="26.743" gradientTransform="matrix(0 -.24 -1.055 0 532.979 557.576)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#B8B8B8" stop-opacity=".498"/><stop offset="1" stop-color="#7F7F7F" stop-opacity="0"/></radialGradient><path opacity=".444" fill="url(#python-original-c)" d="M97.309 119.597c0 3.543-14.816 6.416-33.091 6.416-18.276 0-33.092-2.873-33.092-6.416 0-3.544 14.815-6.417 33.092-6.417 18.275 0 33.091 2.872 33.091 6.417z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.1 KiB |
Reference in New Issue
Block a user