product:create

Table of contents

  1. Introduction
  2. Getting it running
  3. Choosing the SPL model
  4. Creating one while another is selected
  5. Replacing an existing product
  6. What it writes
    1. pyproject.toml
    2. Startup scripts
    3. Docker files
    4. Error pages
    5. Continuous integration
    6. License
  7. Advanced
  8. Reference
    1. Options
    2. Errors
  9. Requirements
  10. See also

Introduction

product:create scaffolds a new product in the workspace. It writes the Docker files, the startup scripts, src/ and a pyproject.toml pinned to an SPL model. The product has no features yet, and nothing is installed or running.

splent product:create my_first_app --spl sample_splent_spl
✅ Product 'my_first_app' created successfully in /workspace/my_first_app

The name must be unique in the workspace and a lowercase Python identifier (letters, digits and underscores, not starting with a digit). It becomes the package under src/.


Getting it running

product:create only writes the scaffold. Four commands take it from there.

splent product:select my_first_app
splent product:configure
splent product:resolve
splent product:derive --dev

product:select makes it the active product. product:configure chooses its features, product:resolve links them and product:derive --dev builds and starts it. To name the product for one command without selecting it, use SPLENT_APP=my_first_app splent <command> (see Another product for one command).

You can also add features one by one with feature:attach or feature:add.


Choosing the SPL model

splent product:create my_first_app

Without --spl, the command lists every model the workspace knows and asks for one. That includes the working copies, the cache, the models other products pin and the sample line sample_splent_spl, whose DOI ships with the CLI.

When the model is not on disk, it is downloaded from UVLHub once. Its pin (mirror, DOI, concept DOI, version and file name) goes to [tool.splent.spl_model], so any clone can fetch it.

For a model this workspace has never seen, give its DOI.

splent product:create my_first_app --spl notes_spl --spl-doi <doi>

A DOI other than the one the workspace knows records only that DOI and the mirror.


Creating one while another is selected

splent product:create my_second_app --no-select

Without --no-select, the command refuses to run while a product is selected, so you never create one by mistake while you work on another. --no-select creates it and leaves your selection as it is.


Replacing an existing product

splent product:create my_first_app --force

The old tree is moved aside and deleted only once the new one is written, so a failure puts it back. --force only replaces a product, never a feature or a SPLENT repository.


What it writes

<workspace>/
└── <product_name>/
    ├── .forgejo/workflows/   ← splent-validate.yml, splent-upgrade.yml
    ├── docker/               ← compose files, Dockerfiles, .env.*.example
    ├── entrypoints/          ← entrypoint.dev.sh, entrypoint.prod.sh
    ├── scripts/              ← the numbered startup scripts
    ├── src/
    │   └── <product_name>/
    │       ├── __init__.py
    │       ├── config.py
    │       ├── errors.py
    │       ├── logging.py
    │       ├── static/          ← css/, js/, favicon.svg
    │       └── templates/       ← base_template.html, 400.html, 401.html
    ├── pyproject.toml
    ├── package.json
    ├── .gitignore
    └── README.md

The ports come from crc32(<product_name>) % 1000 (see Port offset). The web server gets 5000+offset, the database 33060+offset and Locust 8089+offset. The command warns when one is already used by another product of the workspace.

pyproject.toml

[tool.splent]
cli_version = "<cli version>"
features = []
features_dev = []
features_prod = []
spl = "<spl_name>"

[tool.splent.spl_model]
mirror = "uvlhub.io"
doi = "<doi>"
concept_doi = "<concept doi>"
version = "<version>"
file = "<file>.uvl"

[tool.splent.runtime]
splent_framework = "<framework version>"
splent_cli = "<cli version>"
gunicorn = ">=23,<24"

spl names the model, which never lives in the product. It is read from the working copy splent_spl_<name>/, then .splent_cache/spls/, then downloaded by its DOI. [tool.splent.runtime] is what the production image installs besides the features, and product:build keeps it current.

Startup scripts

The scripts run in numbered order. The dev entrypoint runs 00 to 05_0. The prod entrypoint skips 00, 01 and 02_1 and ends with 05_1.

Script Purpose
00_core_requirements_dev.sh Installs the workspace’s framework and CLI editable
00_install_features.sh Installs every declared feature (state installed)
01_compile_assets.sh Builds the frontend assets with Webpack
02_0_db_wait_connection.sh Waits for MariaDB
02_1_db_create_db_test.sh Creates the test database
02_2_db_create_splent_migrations.sh Creates the splent_migrations table
03_initialize_migrations.sh Runs splent db:upgrade once, under a lock of the database server (state migrated). See Startup script integration for SPLENT_MIGRATE_ON_START
04_handle_migrations.sh Seeds a fresh database with splent db:seed -y when RUN_DB_SEED=true, outside production
05_0_start_app_dev.sh Starts the Flask dev server
05_1_start_app_prod.sh Starts the production server

RUN_DB_SEED is true in .env.dev.example and false in .env.prod.example. A development product seeds itself the first time it starts on an empty database, and never after. Run db:seed to seed by hand.

To bring these scripts into an existing product, run product:sync-template.

Docker files

The dev Dockerfile installs the workspace’s framework and CLI editable. The prod Dockerfile builds with the product as its only context, installs the versions of [tool.splent.runtime] and copies the SPL model from docker/spl/ (see the SPL model in the image).

The production compose file runs ${SPLENT_IMAGE:-<product_name>:latest}, the local build unless deploy/.env names a published image. See Environment files for what .env.prod.example carries.

The .gitignore keeps every real env file out of git. It keeps in what product:build writes for a clean clone (deploy/, docker/requirements.lock, splent.contracts.json and docker/spl/), so commit them when a build changes them.

Error pages

templates/400.html and 401.html are the pages the framework renders for those statuses. The 404 and 500 pages come from the theme, through errors.py, so they wear the product’s skin.

There is no 403.html. The framework draws the 403 page inside base_template.html, an application shell, so the user keeps the sidebar. check:product warns about any link to a file of static/ that does not exist.

Continuous integration

.forgejo/workflows/ holds two Forgejo Actions workflows. They run once the product’s repository is on the forge.

Workflow Runs What it does
splent-validate.yml On every push to main, every pull request, and by hand Resolves and validates the product, checks the committed deploy artifacts match product:build --no-image, audits the bundles and runs check:vendor. Other jobs run splent lint --target app and the tests
splent-upgrade.yml On the upgrade_schedule cron (Mondays at 06:00 UTC by default), and by hand Runs feature:upgrade --yes. When pyproject.toml changed, it pushes the branch splent/feature-upgrades and opens a pull request against main

Neither workflow releases or deploys. Features are cloned anonymously, so they must be readable without a token. You can tune both workflows in pyproject.toml.

[tool.splent.ci]
upgrade_schedule = "0 3 * * *"   # every day at 03:00 UTC
tests = false                    # no tests job

Without tests, the tests job is left out when it cannot run a server the features need (see The servers the features need). After a change, run splent product:sync-template <product> --ci to write it into the workflows (see product:sync-template).

While a workflow stays as written, the commands that change the features keep it in step. An edited one is left alone (see Keeping the workflows in step).

License

Nothing product:create writes carries a license, so add the LICENSE you choose. The framework and the features of the SPLENT catalog are LGPL-3.0-or-later, which lets your product carry any license, a closed one included.


Advanced

When the model cannot be downloaded, the product is still created, with a warning to fetch the model before you derive. When no DOI is known, the product only derives on a machine that has the model on disk, so publish it with spl:publish and run spl:pin.


Reference

Options

Option Description
--spl NAME The SPL model to derive from. Without it, the command asks
--spl-doi DOI The DOI of a model this workspace has never seen
--force Replace an existing product
--no-select Create it while another product is selected, and leave that one selected
--features-file PATH Accepted and ignored

Errors

Message What to do
A product is currently selected: <product> Run splent product:deselect, or pass --no-select
'<name>' is not a valid product name Use lowercase letters, digits and underscores, starting with a letter or underscore
The product '<name>' already exists at <path> Pick another name, or pass --force
'<name>' is the name of a SPLENT repository, not a product Pick another name. --force never replaces splent_framework, splent_cli, splent_docs, a splent_feature_* or a splent_spl_*
<path> is not a SPLENT product: <reason> --force only replaces a directory whose pyproject.toml has [tool.splent] and no [tool.splent.contract]. Nothing was changed
This workspace knows no SPL models. Start one with splent spl:create <name>, or pass --spl <name> --spl-doi <doi>

Requirements

  • Detached mode, or --no-select. The command never selects the product it creates. Run splent product:deselect first, or SPLENT_APP= splent product:create <product_name> to run it detached once.

See also

  • The Tutorials walk through a product from creation to deployment.

Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es