frappe-ops-app-lifecycle — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited frappe-ops-app-lifecycle (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
| Command | Purpose | When to Use |
|---|---|---|
bench new-app | Scaffold new app | Starting a new project |
bench get-app URL | Clone from Git | Installing existing app |
bench --site SITE install-app | Install on site | After get-app or new-app |
bench --site SITE remove-app | Uninstall from site | Removing app from site |
bench remove-app | Remove from bench | Removing app entirely |
bench --site SITE migrate | Run patches + sync | After code changes |
bench build | Compile assets | After JS/CSS changes |
bench --site SITE console | Python REPL | Debugging |
bench start | Start dev server | Development |
bench setup production | Configure nginx+supervisor | Deploying to production |
bench new-app my_custom_appInteractive prompts:
octicon octicon-file-directorygreyMITapps/my_custom_app/
├── MANIFEST.in # Files included in Python package
├── README.md # Project readme
├── license.txt # License file
├── requirements.txt # Python dependencies
├── dev-requirements.txt # Dev-only Python deps (v15+)
├── package.json # Node.js dependencies
├── setup.py # Python package config (v14)
├── pyproject.toml # Python package config (v15+)
├── my_custom_app/
│ ├── __init__.py # App version string
│ ├── hooks.py # Framework integration hooks
│ ├── modules.txt # List of app modules
│ ├── patches.txt # Migration patches list
│ ├── config/
│ │ ├── __init__.py
│ │ ├── desktop.py # Desktop/workspace config
│ │ └── docs.py # Documentation config
│ ├── public/ # Static assets → /assets/my_custom_app/
│ │ ├── css/
│ │ └── js/
│ ├── templates/ # Jinja templates
│ └── www/ # Portal pages (URL = path)| File | Purpose | NEVER Forget |
|---|---|---|
__init__.py | Defines __version__ | ALWAYS update before release |
hooks.py | ALL framework integration | Entry point for everything |
modules.txt | Declares app modules | ALWAYS add new modules here |
patches.txt | Migration patch registry | ALWAYS add patches in order |
requirements.txt | Python deps installed on setup | Add pip packages here |
public/ | Static files served by nginx | Accessible at /assets/app_name/ |
www/ | Portal pages | Filename = URL path |
Code → Migrate → Build → Test → Commit# 1. Make code changes (DocTypes, reports, APIs, etc.)
# 2. Migrate — sync DocType schema + run patches
bench --site mysite migrate
# 3. Build — compile JS/CSS assets
bench build --app my_custom_app
# 4. Test — run Python tests
bench --site mysite run-tests --app my_custom_app
# 5. Commit
git -C apps/my_custom_app add -A && git -C apps/my_custom_app commit -m "feat: add feature"ALWAYS run bench migrate after modifying DocType JSON files. ALWAYS run bench build after modifying JS/CSS files.
# Public repo
bench get-app https://github.com/org/my_app
# Specific branch
bench get-app https://github.com/org/my_app --branch develop
# Private repo via SSH
bench get-app [email protected]:org/private_app.git
# Private repo via token (v15+)
bench get-app https://[email protected]/org/private_app.gitAfter get-app, ALWAYS install on the target site:
bench --site mysite install-app my_appget-app clones to apps/ and adds to apps.txt. install-app creates database tables and runs after_install hooks.
Apps are installed in order listed in apps.txt. If App B depends on App A, App A MUST be listed first.
# Install
bench --site mysite install-app my_app
# Verify
bench --site mysite list-apps
# Output: frappe, erpnext, my_app
# Remove from site (keeps code in apps/)
bench --site mysite remove-app my_app
# Remove from bench entirely (deletes code)
bench remove-app my_appDeclare in hooks.py:
required_apps = ["frappe", "erpnext"]Frappe ALWAYS checks required_apps during installation and blocks if dependencies are missing.
bench --site mysite consoleOpens an IPython REPL with Frappe context:
# Query data
frappe.db.sql("SELECT name, status FROM `tabSales Invoice` LIMIT 5", as_dict=True)
# Get a document
doc = frappe.get_doc("Sales Invoice", "SINV-00001")
print(doc.grand_total)
# Test a whitelisted method
from my_app.api import my_function
result = my_function(param="value")
# Check configuration
frappe.get_site_config()
# Auto-reload on code changes (v15+)
# Start with: bench --site mysite console --autoreloadALWAYS use bench console for debugging — NEVER modify production data with raw SQL.
# Enable
bench set-config -g developer_mode 1
# Start dev server (Procfile: web + worker + redis + socketio)
bench startDevelopment mode enables:
dev-requirements.txt dependencies installed# Disable developer mode
bench set-config -g developer_mode 0
# Setup production (nginx + supervisor)
sudo bench setup production USERNAME
# Restart
sudo supervisorctl restart all
# or
sudo systemctl restart supervisorProduction mode:
developer_mode on production sites# Build all apps
bench build
# Build specific app
bench build --app my_custom_app
# Watch mode (auto-rebuild on changes)
bench watchv14 uses build.json in the app root to map source files to bundles:
{
"css/my_app.css": [
"public/css/style.css"
],
"js/my_app.js": [
"public/js/main.js"
]
}# Desk (backend UI)
app_include_js = "my_app.bundle.js" # v15+ bundle syntax
app_include_css = "my_app.bundle.css"
# Portal (website)
web_include_js = "my_app_web.bundle.js"
web_include_css = "my_app_web.bundle.css"
# v14 legacy syntax
app_include_js = "/assets/my_app/js/my_app.js"
app_include_css = "/assets/my_app/css/my_app.css"ALWAYS run bench build after changing JS/CSS files. ALWAYS run bench clear-cache if assets are not updating.
# my_custom_app/__init__.py
__version__ = "1.2.0"ALWAYS use semantic versioning: MAJOR.MINOR.PATCH
The version is read by bench version, displayed in Desk, and used by the Marketplace.
bench version
# frappe 15.23.0
# erpnext 15.18.0
# my_custom_app 1.2.0# my_app/patches/v1_2/update_customer_status.py
import frappe
def execute():
frappe.reload_doc("module_name", "doctype", "customer_extension")
frappe.db.sql("""
UPDATE `tabCustomer Extension`
SET status = 'Active'
WHERE status IS NULL
""")
frappe.db.commit()# patches.txt — v14+ supports sections
[pre_model_sync]
my_app.patches.v1_1.fix_old_data
my_app.patches.v1_2.rename_field_before_schema
[post_model_sync]
my_app.patches.v1_2.update_customer_status
my_app.patches.v1_2.migrate_settingsSection timing (v14+):
[pre_model_sync] — Runs BEFORE DocType schema changes are applied[post_model_sync] — Runs AFTER schema changes (new fields available)[pre_model_sync] by defaulttabPatch Logmy_app.patches.v1_2.fix #2025-03-20frappe.reload_doc() before accessing new/modified DocTypes[post_model_sync] for patches that need new fieldsexecute:frappe.delete_doc("Page", "old-page", ignore_missing=True)# Run all pending patches
bench --site mysite migrate
# Run a specific patch manually in console
bench --site mysite console
>>> from my_app.patches.v1_2.update_customer_status import execute
>>> execute()
>>> frappe.db.commit()setup.py or pyproject.toml with correct metadata__version__ in __init__.pyfrom setuptools import setup, find_packages
setup(
name="my_custom_app",
version="1.0.0",
description="My Custom App for ERPNext",
author="Your Name",
author_email="[email protected]",
packages=find_packages(),
zip_safe=False,
include_package_data=True,
install_requires=["frappe"],
)[project]
name = "my_custom_app"
dynamic = ["version"]
requires-python = ">=3.10,<3.13"
dependencies = ["frappe"]
[build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core:buildapi"# Pull latest code
bench update --pull
# Or update specific app
cd apps/my_custom_app && git pull origin main && cd ../..
# Then migrate (runs patches + syncs schema)
bench --site mysite migrate
# Rebuild assets
bench build --app my_custom_app
# Restart workers
bench restartThe bench update command wraps: backup → pull → requirements → migrate → build → restart.
ALWAYS take a backup before running bench update on production. ALWAYS test updates on staging before applying to production.
frappe-syntax-hooks — Complete hooks.py referencefrappe-core-database — Database and migration patternsfrappe-impl-workspace — Workspace builder, components, and customization~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.