create-procode
v1.0.0
Published
Scaffold a React + TypeScript + Vite app wired to the four ProCode low-code libraries (core, kendo-widget, vs-template, vs-theme).
Maintainers
Readme
create-procode
Scaffold a React 18 + TypeScript + Vite application wired to the four ProCode
low-code libraries — with main.tsx, all five extension points, a working sample
screen and the import-boundary ESLint config already in place.
npm create procode@latest myapp
# or
npx create-procode myappUsage
# interactive — every question has a default; press Enter to accept it
npm create procode@latest
# name positionally, everything else prompted
npx create-procode myapp
# fully specified, no prompts
npx create-procode myapp --app-code MYAPP --base /myapp --yes
# the same through npm create — options go after `--`
npm create procode@latest myapp -- --app-code MYAPP --base /myapp --yesOptions
| Option | Default | What it sets |
|---|---|---|
| <name> / --name <name> | procode-app | Project directory and package name |
| --app-code <code> | derived from the name | VITE_APPLICATION_CODE, and therefore the getAdapter<CODE> global |
| --base <path> | /<name> | Vite base and VITE_HOMEPAGE — kept equal by construction |
| --api-url <url> | QA placeholder | VITE_CENTRAL_SERVICE_BASE_URL; a {APP_CODE} placeholder is filled in |
| --auth-url <url> | https://localhost:7016/token | VITE_AUTH_URL |
| --tool-url <url> | the low-code tool host | VITE_TOOL_BASE_URL |
| --env <name> | InDraft | VITE_PROJECT_ENVIROMENT — InDraft or Published |
| --pm <pm> | auto-detected | npm | pnpm | yarn | bun |
| --no-install | installs | Skip the dependency install |
| --no-git | inits | Skip git init |
| --no-sample | includes it | Skip the sample module screen (leaves the registries empty but wired) |
| --no-lint | includes it | Skip ESLint config and its dev dependencies |
| --no-latest | resolves latest | Use the tested library versions in src/deps.mjs instead of the newest published ones |
| -y, --yes | — | Take defaults for everything not passed as a flag |
| -f, --force | — | Scaffold into a non-empty directory |
| -h, --help / -v, --version | — | |
Anything not passed as a flag is asked interactively, with a default already
filled in — so you can drive this entirely from flags, entirely from prompts, or
any mix. With --yes, or when stdin is not a TTY (CI), nothing is asked.
What you get
myapp/
├── README.md quick start, layout, the rules, pinned versions
├── package.json the newest ProCode releases + the peers they declare
├── index.html root element + favicon link
├── vite.config.ts base, es2020 target, optimizeDeps
├── tsconfig.json / tsconfig.node.json
├── .env / .env.local.example every var the core reads, documented
├── .nvmrc Node 24
├── .eslintrc.cjs the framework's import boundary, at lint time
├── .vscode/extensions.json recommends the ESLint extension
├── .gitignore
├── public/favicon.svg served from the base path
└── src/
├── main.tsx the single integration point — every <Core> prop, typed
├── vite-env.d.ts typed import.meta.env
├── constant.ts APPLICATION_CODE, AuthToken, the three registry enums
├── localizationResolver.ts answers vs-template's date / currency lookups
├── icons.ts registerIcons map
├── routes.ts customRoute
├── Screens/ LoaderElement, ErrorElement, PageNotFoundElement
├── Utils/ responseFormatter, fieldErrorMapper, errorMessageMapper,
│ customStorages, the shared log sink
├── Module/ (1) slug -> schema + Sample/Contacts.json
├── AdapterProvider/ (2) screenId -> adapter + SampleContacts.ts (seeds the rows)
├── Factory/
│ ├── CustomWidgetFactory.tsx (3) custom widgets
│ ├── CustomValidationFactory.tsx (5) custom validations
│ ├── CustomRuleEffectFactory.ts custom rule effects (customRuleEffectProvider)
│ ├── RuleEffects/readOnlyEffect.ts CUS_READONLY, used by the sample
│ ├── Widgets/MyWidget/ a presentation-only widget, composed from library widgets
│ ├── Widgets/SectionNav/ the "Sections" rail for long forms
│ ├── Widgets/Notification/ the app's one toast host
│ ├── Actions/setField.ts CUS_SET_FIELD — a button reaching MODIFY_MODEL
│ ├── Actions/myAction.ts the custom widget's two actions
│ ├── Actions/sampleContacts.ts stand-ins for the API-backed standard actions
│ └── Validations/myValidation.ts
├── CustomAction.ts (4) custom actions, keyed CUS_*
├── CustomAuthorizationProvider.ts
└── assets/styles/index.scss the five-layer SCSS cascade, in orderThe sample screen
One JSON document with no backend, built to show what the stack does declaratively:
| Coordinate | Value |
|---|---|
| slug | sample → src/Module/index.ts |
| schema | src/Module/Sample/Contacts.json |
| screenId | sample-contacts |
| adapter | SampleContacts → src/AdapterProvider/AdapterFactory.ts |
H1heading and a permission-gated Add contact button (CustomAuthorizationProvider).- Three vs-template
BLOCKsummary tiles bound to the view model. - A
GRIDwith sorting, column search, checkbox selection and three bulk actions that openCONFOMATIONALERTBOXwith the selection count spliced into the text; the name column is aNAVIGATIONCELLthat opens the inline editor. Sort and search travel through the grid'sonScreenDataLoadevent, the same pathLOAD_SCREEN_DATAtakes against a server (paging is server-side by design). - An editor shown by a
ruleInterpreterentry, withTEXTBOX,DROPDOWN(inline options),DATE,SWITCH,TEXTAREA, the customMY_WIDGET(composed from the library'sTextBoxandButton), standard validators,MY_VALIDATION, and a conditional Sales region field driven by a second rule. - Save validates every editor field through the core's
executeValidationseam, upserts the row, refreshes the tiles and opensINFOMATIONALERTBOX.
The rows are seeded by the adapter's defaultViewModel. Five small actions in
sampleContacts.ts stand in for DELETE_ASYNC / UPDATE_ASYNC /
FORMSUBMIT_CREATE_ASYNC; each header names the standard key that replaces it,
and the schema's _readme lists the three swaps. The smoke test derives its
registry checks from the schema itself: every widgetType, action key, validation
name, rule target, bulk-action target and permission key in the sample is verified
against the registries.
Pass --no-sample for empty-but-wired registries instead.
What the generated project encodes
These failure modes are prevented by construction rather than left to memory:
- The newest ProCode libraries, with matching peers. At scaffold time the CLI
asks the npm registry for the latest version of each of the four packages, and
writes the React, Kendo, router and axios ranges those versions declare as
peers.
@progress/kendo-theme-defaultandkendo-react-labelsfollow the kendo-react range, and@types/reactfollows the React major. A library release reaches new apps with no generator update. It uses the registry npx was run with, so a mirror works. If the lookup fails (offline, a timeout), the CLI warns and uses the tested set insrc/deps.mjs;--no-latestchooses that set on purpose. - React stays on the major the libraries declare. Today that is
react ^18.2.0; 19 installs cleanly then fails at runtime. react-reduxis never declared. It is bundled inside the core. Declaring it and floating to 9.x gives two Provider contexts, and every screen renders blank with nothing in the console.@progress/kendo-theme-defaultis included. It is a missing peer declaration, not an optional extra — the widget library's SCSS imports it by name, so omitting it is a hard build failure on the firstnpm run dev.build.target: es2020. Ates2015the downlevel transform miscompiles aconstdeclaring two rest patterns at once, and Kendo's date picker throws "Cannot destructure 'null' as it is null" in built bundles only.baseandVITE_HOMEPAGEare written from one value, so they cannot drift.VITE_APPLICATION_CODEis validated as a JS identifier fragment, because it becomes thegetAdapter<CODE>global the core resolves by name.- The SCSS import order is fixed and annotated — it is the cascade, and reordering gives "Undefined variable" or silently wrong spacing.
main.tsxordering is annotated as load-bearing, withvalidateStatusfirst, and the smoke test asserts it.- Every
<Core>seam is wired, and type-checked.<Core>acceptsany, so a misspelled prop compiles and is silently dropped.main.tsxbuilds a typedCorePropsobject instead, and the smoke test asserts every seam is in it: widget, action, validation and rule-effect providers, authorization, routes, storages, response formatting, 422 field errors, error-message text, the 401 handler, the loader / error / 404 screens and the notification host. The core and widget loggers,registerIconsandregisterLocalizationare registered before render. Deprecated props are never emitted. - Rule effects are injectable.
CustomRuleEffectFactoryis resolved before the standard effects, so it can add effects or overrideSHOW/HIDE. The sample uses itsCUS_READONLY, and the smoke test checks every effect a schema names is standard or registered. - Factories use
Maplookups. On a plain object, a key like"constructor"returns anObject.prototypefunction the core would invoke. - The core stylesheet builds on Linux. procode-lowcode-core exports
./stylesassrc/Assets/…, but shipssrc/assets/…. A Vite alias points the import at the real path, so CI and Docker builds stop failing with "Can't find stylesheet to import". - The localization seam is registered.
main.tsxcalls vs-template'sregisterLocalization()withsrc/localizationResolver.tsbefore render. It answers everyLocalizationKeyfrom one date pattern, includingdateFormatSections. Without that key, a date box's placeholder changes to Kendo's long form on focus. - The framework's import boundary is enforced at lint time. Custom code is still
built from library parts: a control
inside a custom widget is the library's
TextBox/Dropdown/Buttonfromprocode-lowcode-kendo-widget/widgets, never@progress/kendo-react-*; icons go throughloadIcon/registerIcons, never a second icon set; navigation isNAVIGATE/MENUNAVIGATE, neverwindow.location; toasts areresponseConfigor the one Notification host, neverreact-toastifyelsewhere;react-redux,react-router-domandprocode-*/src/…deep paths are library internals..eslintrc.cjsrefuses any of those. It honours one escape hatch — a// procode-escalation: <key> — L<n> cannot compose this because <limitation>comment — for a control the library genuinely does not wrap, so every exception is greppable. The sampleMyWidgetis itself composed from library widgets, so the first custom widget a developer copies is a correct one.
Maintenance
Library versions
New apps get the latest ProCode releases automatically, so a library release
needs no generator change. src/deps.mjs holds two things that
still need care:
- The tested fallback set: used with
--no-latestor when the registry is unreachable. Refresh it after a library release has been verified, then run the smoke test with--install. Current pins: core^2.5.0, kendo-widget^2.11.0, vs-template^2.13.1, vs-theme^2.6.0(the latest releases as of 2026-10-12). UNDECLARED_BUT_REQUIRED: packages the libraries need but do not declare as peers, so the lookup cannot find them. Add one here if a release starts needing a new one.
The generated README's version table is rendered from whichever set was used.
Test
npm run smoke # scaffold + structural assertions, seconds
node scripts/smoke-test.mjs --install # also npm install + npm run build
node scripts/smoke-test.mjs --keep # leave the generated app for inspectionThe --install run is the only one that catches a dependency set that resolves on
paper but not in practice.
Publishing
npm version patch
npm publishfiles in package.json ships bin/, src/ and templates/ — about 50 KB packed.
Requirements
Node 18+ to run the generator. Node 24 recommended for the generated app. Zero runtime dependencies — the CLI uses only Node builtins.
