create-procode 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +240 -2
- package/bin/create-procode.mjs +9 -0
- package/package.json +30 -4
- package/src/args.mjs +139 -0
- package/src/cli.mjs +392 -0
- package/src/deps.mjs +195 -0
- package/src/log.mjs +23 -0
- package/src/prompt.mjs +60 -0
- package/src/scaffold.mjs +104 -0
- package/templates/base/README.md.tmpl +183 -0
- package/templates/base/_dotenv.local.example.tmpl +8 -0
- package/templates/base/_dotenv.tmpl +41 -0
- package/templates/base/_doteslintrc.cjs.tmpl +125 -0
- package/templates/base/_dotgitignore.tmpl +32 -0
- package/templates/base/_dotnvmrc.tmpl +1 -0
- package/templates/base/_dotvscode/extensions.json +3 -0
- package/templates/base/index.html.tmpl +18 -0
- package/templates/base/package.json.tmpl +15 -0
- package/templates/base/public/favicon.svg +4 -0
- package/templates/base/src/AdapterProvider/AdapterFactory.ts.tmpl +21 -0
- package/templates/base/src/AdapterProvider/SampleContacts.ts.tmpl +136 -0
- package/templates/base/src/CustomAction.ts.tmpl +47 -0
- package/templates/base/src/CustomAuthorizationProvider.ts.tmpl +77 -0
- package/templates/base/src/Factory/Actions/myAction.ts.tmpl +51 -0
- package/templates/base/src/Factory/Actions/sampleContacts.ts.tmpl +244 -0
- package/templates/base/src/Factory/Actions/setField.ts.tmpl +20 -0
- package/templates/base/src/Factory/CustomRuleEffectFactory.ts.tmpl +30 -0
- package/templates/base/src/Factory/CustomValidationFactory.tsx.tmpl +24 -0
- package/templates/base/src/Factory/CustomWidgetFactory.tsx.tmpl +43 -0
- package/templates/base/src/Factory/RuleEffects/readOnlyEffect.ts.tmpl +27 -0
- package/templates/base/src/Factory/Validations/myValidation.ts.tmpl +52 -0
- package/templates/base/src/Factory/Widgets/MyWidget/index.tsx.tmpl +101 -0
- package/templates/base/src/Factory/Widgets/Notification/index.tsx.tmpl +53 -0
- package/templates/base/src/Factory/Widgets/Notification/notifiFunc.ts.tmpl +9 -0
- package/templates/base/src/Factory/Widgets/SectionNav/index.tsx +193 -0
- package/templates/base/src/Factory/Widgets/SectionNav/style.scss +139 -0
- package/templates/base/src/Module/Sample/Contacts.json.tmpl +965 -0
- package/templates/base/src/Module/index.ts.tmpl +26 -0
- package/templates/base/src/Screens/index.tsx.tmpl +45 -0
- package/templates/base/src/Utils/errorMessages.ts.tmpl +35 -0
- package/templates/base/src/Utils/fieldErrorMapper.ts.tmpl +46 -0
- package/templates/base/src/Utils/logger.ts.tmpl +21 -0
- package/templates/base/src/Utils/responseFormatter.ts.tmpl +39 -0
- package/templates/base/src/Utils/storages.ts.tmpl +35 -0
- package/templates/base/src/assets/styles/index.scss.tmpl +146 -0
- package/templates/base/src/constant.ts.tmpl +53 -0
- package/templates/base/src/icons.ts.tmpl +17 -0
- package/templates/base/src/localizationResolver.ts.tmpl +48 -0
- package/templates/base/src/main.tsx.tmpl +136 -0
- package/templates/base/src/routes.ts.tmpl +20 -0
- package/templates/base/src/vite-env.d.ts.tmpl +20 -0
- package/templates/base/tsconfig.json.tmpl +30 -0
- package/templates/base/tsconfig.node.json.tmpl +11 -0
- package/templates/base/vite.config.ts.tmpl +61 -0
package/src/scaffold.mjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
export const TEMPLATES_DIR = path.resolve(__dirname, "..", "templates");
|
|
7
|
+
export const BASE_DIR = path.join(TEMPLATES_DIR, "base");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Template naming conventions, applied when a template file lands on disk:
|
|
11
|
+
* foo.ts.tmpl -> foo.ts (token substitution runs)
|
|
12
|
+
* _dotgitignore -> .gitignore (leading `_dot` becomes `.`)
|
|
13
|
+
* anything else -> copied byte-for-byte, no substitution
|
|
14
|
+
*/
|
|
15
|
+
export function targetName(name) {
|
|
16
|
+
let out = name;
|
|
17
|
+
if (out.startsWith("_dot")) out = "." + out.slice(4);
|
|
18
|
+
if (out.endsWith(".tmpl")) out = out.slice(0, -5);
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const TOKEN_RE = /__([A-Z0-9_]+)__/g;
|
|
23
|
+
|
|
24
|
+
export function substitute(text, tokens) {
|
|
25
|
+
return text.replace(TOKEN_RE, (match, key) =>
|
|
26
|
+
Object.prototype.hasOwnProperty.call(tokens, key) ? String(tokens[key]) : match,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Files that must never be substituted even though they are text. */
|
|
31
|
+
const isTemplated = (name) => name.endsWith(".tmpl");
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Recursively copy `from` into `to`, renaming and substituting as it goes.
|
|
35
|
+
* Returns the list of created project-relative paths.
|
|
36
|
+
*/
|
|
37
|
+
export function copyTree(from, to, tokens, created = [], rootTo = to) {
|
|
38
|
+
fs.mkdirSync(to, { recursive: true });
|
|
39
|
+
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
40
|
+
const src = path.join(from, entry.name);
|
|
41
|
+
const dest = path.join(to, targetName(entry.name));
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
copyTree(src, dest, tokens, created, rootTo);
|
|
44
|
+
} else {
|
|
45
|
+
if (isTemplated(entry.name)) {
|
|
46
|
+
const text = fs.readFileSync(src, "utf8");
|
|
47
|
+
fs.writeFileSync(dest, substitute(text, tokens), "utf8");
|
|
48
|
+
} else {
|
|
49
|
+
fs.copyFileSync(src, dest);
|
|
50
|
+
}
|
|
51
|
+
created.push(path.relative(rootTo, dest).split(path.sep).join("/"));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return created;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A directory is usable as a target if it does not exist, or is empty, or
|
|
58
|
+
* holds only benign files a user tends to create first. */
|
|
59
|
+
const BENIGN = new Set([".git", ".gitignore", ".idea", ".vscode", "README.md", "LICENSE", ".DS_Store"]);
|
|
60
|
+
|
|
61
|
+
export function inspectTarget(dir) {
|
|
62
|
+
if (!fs.existsSync(dir)) return { exists: false, empty: true, conflicts: [] };
|
|
63
|
+
const stat = fs.statSync(dir);
|
|
64
|
+
if (!stat.isDirectory()) return { exists: true, empty: false, notADirectory: true, conflicts: [dir] };
|
|
65
|
+
const entries = fs.readdirSync(dir);
|
|
66
|
+
const conflicts = entries.filter((e) => !BENIGN.has(e));
|
|
67
|
+
return { exists: true, empty: conflicts.length === 0, conflicts };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function writeJson(file, value) {
|
|
71
|
+
fs.writeFileSync(file, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** npm forbids uppercase and a few characters in a package name. */
|
|
75
|
+
export function toPackageName(input) {
|
|
76
|
+
return (
|
|
77
|
+
input
|
|
78
|
+
.trim()
|
|
79
|
+
.toLowerCase()
|
|
80
|
+
.replace(/[^a-z0-9._~-]+/g, "-")
|
|
81
|
+
.replace(/^[-_.]+|[-_.]+$/g, "")
|
|
82
|
+
.slice(0, 214) || "procode-app"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** `myapp` / `my-app/` / `/my-app` all normalise to `/my-app` (no trailing slash). */
|
|
87
|
+
export function toBasePath(input) {
|
|
88
|
+
const raw = String(input).trim();
|
|
89
|
+
|
|
90
|
+
// Git Bash / MSYS rewrites a leading-slash argument into a Windows path before
|
|
91
|
+
// node ever sees it, so `--base /myapp` arrives as
|
|
92
|
+
// `C:/Program Files/Git/myapp`. Silently accepting that produces a Vite base
|
|
93
|
+
// nobody can serve, and the mistake only shows up as a 404 at dev time.
|
|
94
|
+
if (/^\/?[A-Za-z]:[\\/]/.test(raw)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`--base "${raw}" looks like a Windows path, not a URL path.\n` +
|
|
97
|
+
" Git Bash rewrites a leading-slash argument into a filesystem path. Either drop the\n" +
|
|
98
|
+
" leading slash (--base myapp), or prefix the command with MSYS_NO_PATHCONV=1.",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const trimmed = raw.replace(/^\/+|\/+$/g, "");
|
|
103
|
+
return "/" + trimmed;
|
|
104
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# __APP_NAME__
|
|
2
|
+
|
|
3
|
+
A ProCode low-code application — React 18 + TypeScript + Vite on `procode-lowcode-core`,
|
|
4
|
+
`procode-lowcode-kendo-widget`, `procode-vs-template` and `procode-vs-theme`.
|
|
5
|
+
|
|
6
|
+
Scaffolded with `create-procode` v__GENERATOR_VERSION__.
|
|
7
|
+
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install
|
|
12
|
+
npm run dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then open **http://localhost:5173__BASE_PATH__/sample**
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
http://localhost:5173__BASE_PATH__/sample
|
|
19
|
+
^^^^^^^^^^^^^^ ^^^^^^
|
|
20
|
+
base slug
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The base comes from `vite.config.ts` (and must equal `VITE_HOMEPAGE` in `.env`); the
|
|
24
|
+
slug comes from `src/Module/index.ts`.
|
|
25
|
+
|
|
26
|
+
## Kendo licence
|
|
27
|
+
|
|
28
|
+
Without a licence key, Kendo shows a banner and a console warning on every page.
|
|
29
|
+
Put the key in `kendo-ui-license.txt` at the project root (it is gitignored), or set
|
|
30
|
+
the `KENDO_UI_LICENSE` environment variable in CI. Then run `npm install`, or
|
|
31
|
+
`npx kendo-ui-license activate`.
|
|
32
|
+
|
|
33
|
+
## Before it will load real data
|
|
34
|
+
|
|
35
|
+
1. Point `VITE_CENTRAL_SERVICE_BASE_URL` in `.env` at your environment.
|
|
36
|
+
2. Copy `.env.local.example` to `.env.local` and put a dev token in
|
|
37
|
+
`VITE_HARDCODED_TOKEN` — or sign in so the `AuthToken` cookie is set.
|
|
38
|
+
**`.env.local` is gitignored. Never commit a token to `.env`.**
|
|
39
|
+
3. Follow the `_readme` at the top of `src/Module/Sample/Contacts.json`: add an
|
|
40
|
+
`apiConfig`, bind `LOAD_SCREEN_DATA`, and swap the `CUS_SAMPLE_*` stand-ins for
|
|
41
|
+
`DELETE_ASYNC` / `UPDATE_ASYNC` / `FORMSUBMIT_CREATE_ASYNC`.
|
|
42
|
+
|
|
43
|
+
## Scripts
|
|
44
|
+
|
|
45
|
+
| Script | What it does |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `npm run dev` | Vite dev server on port 5173 |
|
|
48
|
+
| `npm run build` | `tsc` then `vite build` |
|
|
49
|
+
| `npm run preview` | Serve the built bundle |
|
|
50
|
+
| `npm run lint` | ESLint over `src/` |
|
|
51
|
+
|
|
52
|
+
## Layout
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
src/
|
|
56
|
+
├── main.tsx the SINGLE integration point — every <Core> prop, typed
|
|
57
|
+
├── constant.ts APPLICATION_CODE, AuthToken, WidgetTypes, ValidationTypes, RuleEffectTypes
|
|
58
|
+
├── CustomAction.ts (4) custom actions, keyed CUS_*
|
|
59
|
+
├── CustomAuthorizationProvider.ts screen / widget / action gates
|
|
60
|
+
├── localizationResolver.ts date pattern, currency symbol, … for vs-template widgets
|
|
61
|
+
├── icons.ts app SVG icons, registered with registerIcons
|
|
62
|
+
├── routes.ts extra routes (customRoute)
|
|
63
|
+
├── Screens/index.tsx loader, error and 404 screens
|
|
64
|
+
├── Utils/
|
|
65
|
+
│ ├── responseFormatter.ts reshapes every response body before adapters see it
|
|
66
|
+
│ ├── fieldErrorMapper.ts 422 body -> per-field messages
|
|
67
|
+
│ ├── errorMessages.ts fallback toast text per verb and status
|
|
68
|
+
│ ├── storages.ts extra STORAGE:: stores (MEMORY)
|
|
69
|
+
│ └── logger.ts one log sink for the core and the widget library
|
|
70
|
+
├── Module/
|
|
71
|
+
│ ├── index.ts (1) slug -> schema
|
|
72
|
+
│ └── Sample/Contacts.json the sample screen — grid, bulk actions, editor, dialogs, rules
|
|
73
|
+
├── AdapterProvider/
|
|
74
|
+
│ ├── AdapterFactory.ts (2) screenId -> adapter
|
|
75
|
+
│ └── SampleContacts.ts a pure translator, no HTTP; seeds the sample rows
|
|
76
|
+
├── Factory/
|
|
77
|
+
│ ├── CustomWidgetFactory.tsx (3) custom widgets, keyed by WidgetTypes
|
|
78
|
+
│ ├── CustomValidationFactory.tsx (5) custom validations, keyed by ValidationTypes
|
|
79
|
+
│ ├── CustomRuleEffectFactory.ts custom rule effects, keyed by RuleEffectTypes
|
|
80
|
+
│ ├── RuleEffects/readOnlyEffect.ts CUS_READONLY — read-only while a condition holds
|
|
81
|
+
│ ├── Widgets/MyWidget/ a presentation-only widget, composed from library widgets
|
|
82
|
+
│ ├── Widgets/Notification/ the app's one toast host
|
|
83
|
+
│ ├── Actions/setField.ts CUS_SET_FIELD — lets a button reach MODIFY_MODEL
|
|
84
|
+
│ ├── Actions/myAction.ts the custom widget's two actions
|
|
85
|
+
│ ├── Actions/sampleContacts.ts the sample's stand-ins for the API-backed standard actions
|
|
86
|
+
│ └── Validations/myValidation.ts
|
|
87
|
+
└── assets/styles/index.scss the SCSS cascade — import order matters
|
|
88
|
+
public/ copied verbatim to the build, served from the base path
|
|
89
|
+
.env / .env.local.example every variable the core reads, documented
|
|
90
|
+
.eslintrc.cjs the framework's import boundary, enforced at lint time
|
|
91
|
+
.vscode/extensions.json recommends the ESLint extension
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Reference files in `public/` root-relative — `/favicon.svg`, not
|
|
95
|
+
`__BASE_PATH_SLASH__favicon.svg`. Vite prepends `base` itself, so writing it
|
|
96
|
+
yourself doubles it.
|
|
97
|
+
|
|
98
|
+
## The sample screen
|
|
99
|
+
|
|
100
|
+
It demonstrates all five extension points at once, and its four coordinates must agree:
|
|
101
|
+
|
|
102
|
+
| Coordinate | Value | File |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| slug | `sample` | `src/Module/index.ts` |
|
|
105
|
+
| schema | — | `src/Module/Sample/Contacts.json` |
|
|
106
|
+
| screenId | `sample-contacts` | inside that JSON |
|
|
107
|
+
| adapter | `SampleContacts` | `src/AdapterProvider/AdapterFactory.ts` |
|
|
108
|
+
|
|
109
|
+
One JSON document, no backend, and everything on the page is declared in it:
|
|
110
|
+
|
|
111
|
+
- a heading, a subtitle and an **Add contact** button gated by a permission key;
|
|
112
|
+
- three `BLOCK` summary tiles that stay live;
|
|
113
|
+
- a `GRID` with sorting, column search, checkbox selection and three bulk actions
|
|
114
|
+
(Deactivate / Activate / Delete) that open the confirmation dialog with the
|
|
115
|
+
selection count in the text — sorting and searching go through the grid's
|
|
116
|
+
`onScreenDataLoad` event exactly as they would against a server;
|
|
117
|
+
- a click on a name that opens the inline editor for that row;
|
|
118
|
+
- an editor with `TEXTBOX`, `DROPDOWN`, `DATE`, `SWITCH`, `TEXTAREA` and the custom
|
|
119
|
+
`MY_WIDGET`, standard validators, and a *Sales region* field that a rule shows only
|
|
120
|
+
for the Sales department;
|
|
121
|
+
- a custom rule effect, `CUS_READONLY`, that makes *Notes* read-only while the
|
|
122
|
+
contact is inactive;
|
|
123
|
+
- a **Save** that validates with the framework's validators, adds or updates the row
|
|
124
|
+
in the grid and opens the information dialog.
|
|
125
|
+
|
|
126
|
+
The rows are seeded by the adapter. The `CUS_SAMPLE_*` actions stand in for the
|
|
127
|
+
API-backed standard actions and say so in their headers; the schema's `_readme`
|
|
128
|
+
lists the three swaps that take the screen live.
|
|
129
|
+
|
|
130
|
+
## The one rule worth knowing up front
|
|
131
|
+
|
|
132
|
+
**All application data access is `apiConfig` plus a standard action bound to a schema
|
|
133
|
+
event.** An `import axios` or a bare `fetch(` anywhere under `src/` outside `main.tsx`
|
|
134
|
+
silently forfeits the loading counter and skeletons, status-code toasts, 422 →
|
|
135
|
+
field-error routing, action chainability, a schema-visible endpoint, and
|
|
136
|
+
per-environment retargeting without a code change.
|
|
137
|
+
|
|
138
|
+
A widget renders; it does not fetch.
|
|
139
|
+
|
|
140
|
+
## The second rule: custom code is still made of library parts
|
|
141
|
+
|
|
142
|
+
A custom widget renders the library's `TextBox` / `Dropdown` / `Button` from
|
|
143
|
+
`procode-lowcode-kendo-widget/widgets` — never `import { … } from "@progress/kendo-react-*"`
|
|
144
|
+
and never a raw `<input>`. Icons go through `loadIcon` / `registerIcons`; navigation is
|
|
145
|
+
`NAVIGATE` / `MENUNAVIGATE`; a toast is `responseConfig` or the Notification host; the
|
|
146
|
+
store is `props.viewModel`. `react-redux`, `react-router-dom`, `react-toastify` and
|
|
147
|
+
`procode-*/src/…` deep paths are library internals. `npm run lint` refuses code that
|
|
148
|
+
breaks this; the one exception — a control the library genuinely does not wrap — is
|
|
149
|
+
marked with a `// procode-escalation: …` comment so it can be found with `grep`.
|
|
150
|
+
|
|
151
|
+
## Localization
|
|
152
|
+
|
|
153
|
+
vs-template widgets ask the app for per-user values — date pattern, date
|
|
154
|
+
placeholder, currency symbol and decimals — through `src/localizationResolver.ts`,
|
|
155
|
+
which `main.tsx` registers with `registerLocalization()` before render. It returns
|
|
156
|
+
`undefined` for every key out of the box, so each widget uses its own default. Answer
|
|
157
|
+
a key there (from a token claim, a cached settings payload, or a constant) and every
|
|
158
|
+
widget that asks for it picks the value up on its next render.
|
|
159
|
+
|
|
160
|
+
## Versions
|
|
161
|
+
|
|
162
|
+
The newest ProCode releases when this app was scaffolded, with the React, Kendo,
|
|
163
|
+
router and axios ranges those releases declare as peers:
|
|
164
|
+
|
|
165
|
+
| Package | Version |
|
|
166
|
+
|---|---|
|
|
167
|
+
__PROCODE_VERSIONS__
|
|
168
|
+
|
|
169
|
+
Node 24, React __REACT_RANGE__. **Keep React on the major the ProCode libraries declare as
|
|
170
|
+
a peer** — a newer major installs cleanly, then fails at runtime.
|
|
171
|
+
|
|
172
|
+
To move to newer ProCode releases later, upgrade the four together, then check for
|
|
173
|
+
peer warnings:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
npm install procode-lowcode-core@latest procode-lowcode-kendo-widget@latest procode-vs-template@latest procode-vs-theme@latest
|
|
177
|
+
npm ls --depth=0
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Do not add `react-redux`** to `package.json`. It is bundled inside
|
|
181
|
+
`procode-lowcode-core` (on 8.x); a second copy gives two Provider contexts and every
|
|
182
|
+
screen renders blank with nothing in the console. The `_procodeBundled` block in
|
|
183
|
+
`package.json` lists every package bundled this way.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Copy to .env.local (gitignored) and fill in.
|
|
2
|
+
#
|
|
3
|
+
# AuthToken = VITE_HARDCODED_TOKEN ?? the AuthToken cookie. Without one of the
|
|
4
|
+
# two, every request 401s and every screen is empty.
|
|
5
|
+
VITE_HARDCODED_TOKEN=""
|
|
6
|
+
|
|
7
|
+
# Point at a local API without touching the committed .env:
|
|
8
|
+
# VITE_CENTRAL_SERVICE_BASE_URL="https://localhost:7016/lowcodev2/api/__APP_CODE__/"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# ---------------------------------------------------------------------------
|
|
2
|
+
# __APP_NAME__ — committed defaults.
|
|
3
|
+
#
|
|
4
|
+
# procode-lowcode-core reads every one of these directly. A missing value does
|
|
5
|
+
# NOT throw: it produces a wrong URL or an empty screen, so treat a blank here
|
|
6
|
+
# as a bug rather than a default.
|
|
7
|
+
#
|
|
8
|
+
# Machine-specific values and ANY token belong in .env.local (gitignored).
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
# Where application data comes from. The <APP_CODE> segment is part of the path.
|
|
12
|
+
VITE_CENTRAL_SERVICE_BASE_URL="__API_BASE_URL__"
|
|
13
|
+
|
|
14
|
+
# Must equal the code main.tsx builds the adapter global from. The core resolves
|
|
15
|
+
# that global by name as "getAdapter" + VITE_APPLICATION_CODE — if the two
|
|
16
|
+
# diverge, EVERY adapter silently falls back to a dummy and no screen has data.
|
|
17
|
+
VITE_APPLICATION_CODE="__APP_CODE__"
|
|
18
|
+
|
|
19
|
+
# Must match `base` in vite.config.ts.
|
|
20
|
+
VITE_HOMEPAGE="__BASE_PATH__"
|
|
21
|
+
|
|
22
|
+
# InDraft | Published
|
|
23
|
+
VITE_PROJECT_ENVIROMENT="__PROJECT_ENV__"
|
|
24
|
+
|
|
25
|
+
# The low-code tool that stores and serves schemas.
|
|
26
|
+
VITE_TOOL_BASE_URL="__TOOL_BASE_URL__"
|
|
27
|
+
VITE_TOOL_SCHEMA_FETCH="https://lowcodeapi-399313.uc.r.appspot.com/api/screenRetriever/getScreenBySlug"
|
|
28
|
+
|
|
29
|
+
# Auth
|
|
30
|
+
VITE_AUTH_URL="__AUTH_URL__"
|
|
31
|
+
# VITE_BASE_LOGOUT_URL=""
|
|
32
|
+
|
|
33
|
+
# Slugs for the shared header and footer schemas.
|
|
34
|
+
VITE_HEADER_SLUG="header"
|
|
35
|
+
VITE_FOOTER_SLUG="footer"
|
|
36
|
+
|
|
37
|
+
# How long a notification stays up, in ms.
|
|
38
|
+
# VITE_NOTIFICATION_VISIBLE_DURATION="3000"
|
|
39
|
+
|
|
40
|
+
# A personal dev token belongs in .env.local, NOT here:
|
|
41
|
+
# VITE_HARDCODED_TOKEN="eyJhbGciOi..."
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// The `no-restricted-imports` block below enforces the framework's import
|
|
3
|
+
// boundary: custom code is built from library parts, never from the packages
|
|
4
|
+
// those parts wrap.
|
|
5
|
+
//
|
|
6
|
+
// Every entry has one legitimate exception: a control the library genuinely
|
|
7
|
+
// does not wrap. Disable the line with the escalation marker in the
|
|
8
|
+
// description, naming the module and the limitation:
|
|
9
|
+
//
|
|
10
|
+
// // eslint-disable-next-line no-restricted-imports -- procode-escalation: @progress/kendo-react-layout — L4 cannot compose this because the catalog has no stepper widget.
|
|
11
|
+
// import { Stepper } from "@progress/kendo-react-layout";
|
|
12
|
+
//
|
|
13
|
+
// `grep -rn "procode-escalation:" src` then lists every exception in the app.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
const KENDO = {
|
|
17
|
+
group: ["@progress/kendo-react-*", "@progress/kendo-svg-icons"],
|
|
18
|
+
message:
|
|
19
|
+
"Library boundary: render the library's widget from procode-lowcode-kendo-widget/widgets, or loadIcon()/registerIcons for a glyph. A control the catalog does not wrap: `-- procode-escalation: <module> — L4 cannot compose this because <limitation>`.",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ICON_SETS = {
|
|
23
|
+
group: ["@fortawesome/*", "react-icons", "react-icons/*", "lucide-react", "@heroicons/*", "@tabler/icons*"],
|
|
24
|
+
message:
|
|
25
|
+
"A second icon set is parallel infrastructure. Use the ICON widget, loadIcon(\"name\"), or registerIcons({ name: svg }) once for a glyph Kendo lacks.",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const LIBRARY_INTERNALS = {
|
|
29
|
+
group: ["react-redux", "react-router-dom"],
|
|
30
|
+
message:
|
|
31
|
+
"Library internals, not app imports. A widget reads props.viewModel; an action uses props.navigate / props.eventService; navigation is NAVIGATE / NAVIGATE_BACK / MENUNAVIGATE.",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const TOAST = {
|
|
35
|
+
group: ["react-toastify"],
|
|
36
|
+
message:
|
|
37
|
+
"One toast host: use responseConfig on the verb block, or the helper in src/Factory/Widgets/Notification/notifiFunc.ts.",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const HTTP = {
|
|
41
|
+
group: ["axios"],
|
|
42
|
+
message:
|
|
43
|
+
"All data access is apiConfig + a standard action bound to a schema event. Only src/main.tsx imports axios.",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const DEEP_PATHS = {
|
|
47
|
+
group: [
|
|
48
|
+
"procode-lowcode-core/src/*",
|
|
49
|
+
"procode-lowcode-core/dist/*",
|
|
50
|
+
"procode-lowcode-kendo-widget/src/*",
|
|
51
|
+
"procode-lowcode-kendo-widget/dist/*",
|
|
52
|
+
"procode-vs-template/src/*",
|
|
53
|
+
"procode-vs-template/dist/*",
|
|
54
|
+
"procode-vs-theme/src/*",
|
|
55
|
+
"procode-vs-theme/dist/*",
|
|
56
|
+
],
|
|
57
|
+
message:
|
|
58
|
+
"Deep import into a package internal. Import only the declared exports subpaths.",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const OTHER_TOOLKITS = {
|
|
62
|
+
group: ["@mui/*", "antd", "antd/*", "react-bootstrap", "@chakra-ui/*", "primereact/*", "styled-components", "@emotion/*", "@radix-ui/*", "@headlessui/*"],
|
|
63
|
+
message:
|
|
64
|
+
"A second UI toolkit or styling system is parallel infrastructure. Widgets come from procode-lowcode-kendo-widget; styling from procode-vs-theme utilities and widgetStyle.",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const ALL = [KENDO, ICON_SETS, LIBRARY_INTERNALS, TOAST, HTTP, DEEP_PATHS, OTHER_TOOLKITS];
|
|
68
|
+
const without = (...skip) => ALL.filter((rule) => !skip.includes(rule));
|
|
69
|
+
|
|
70
|
+
const WINDOW_LOCATION = [
|
|
71
|
+
"error",
|
|
72
|
+
{
|
|
73
|
+
object: "window",
|
|
74
|
+
property: "location",
|
|
75
|
+
message:
|
|
76
|
+
"Navigation is NAVIGATE / NAVIGATE_BACK, MENUNAVIGATE EXTERNAL to leave the SPA, or props.navigate in an action; a query/route param is a QUERYPARAMS / ROUTEPARAMS policy.",
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
root: true,
|
|
82
|
+
env: { browser: true, es2020: true },
|
|
83
|
+
extends: [
|
|
84
|
+
"eslint:recommended",
|
|
85
|
+
"plugin:@typescript-eslint/recommended",
|
|
86
|
+
"plugin:react-hooks/recommended",
|
|
87
|
+
],
|
|
88
|
+
ignorePatterns: ["dist", ".eslintrc.cjs"],
|
|
89
|
+
parser: "@typescript-eslint/parser",
|
|
90
|
+
plugins: ["react-refresh"],
|
|
91
|
+
rules: {
|
|
92
|
+
"react-refresh/only-export-components": [
|
|
93
|
+
"warn",
|
|
94
|
+
{ allowConstantExport: true },
|
|
95
|
+
],
|
|
96
|
+
// Widget, action and adapter props cross a schema boundary that TypeScript
|
|
97
|
+
// cannot see, so `any` is the honest type at those seams.
|
|
98
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
99
|
+
// The framework fixes several signatures — getSchemaBySlugService(slug, appCode),
|
|
100
|
+
// a validator's (value, props, viewModel), an adapter hook's (props) — so an
|
|
101
|
+
// implementation that needs only the first argument still has to declare the
|
|
102
|
+
// rest. A leading underscore marks them as deliberately unused.
|
|
103
|
+
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
|
104
|
+
"no-restricted-imports": ["error", { patterns: ALL }],
|
|
105
|
+
"no-restricted-properties": WINDOW_LOCATION,
|
|
106
|
+
},
|
|
107
|
+
overrides: [
|
|
108
|
+
{
|
|
109
|
+
// The single integration point: the one `import axios` (validateStatus)
|
|
110
|
+
// and the one place the Kendo/router/redux packages may be touched.
|
|
111
|
+
files: ["src/main.tsx"],
|
|
112
|
+
rules: {
|
|
113
|
+
"no-restricted-imports": ["error", { patterns: without(HTTP, LIBRARY_INTERNALS) }],
|
|
114
|
+
"no-restricted-properties": "off",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
// The app's ONE toast host.
|
|
119
|
+
files: ["src/Factory/Widgets/Notification/**"],
|
|
120
|
+
rules: {
|
|
121
|
+
"no-restricted-imports": ["error", { patterns: without(TOAST) }],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
|
|
9
|
+
node_modules
|
|
10
|
+
dist
|
|
11
|
+
dist-ssr
|
|
12
|
+
*.local
|
|
13
|
+
|
|
14
|
+
# Local env — this is where a personal dev token belongs. Never .env.
|
|
15
|
+
.env.local
|
|
16
|
+
.env.*.local
|
|
17
|
+
|
|
18
|
+
# Kendo UI licence key. @progress/kendo-licensing activates it on npm install
|
|
19
|
+
# from this file or the KENDO_UI_LICENSE environment variable.
|
|
20
|
+
kendo-ui-license.txt
|
|
21
|
+
kendo-ui-license.js
|
|
22
|
+
|
|
23
|
+
# Editor
|
|
24
|
+
.vscode/*
|
|
25
|
+
!.vscode/extensions.json
|
|
26
|
+
.idea
|
|
27
|
+
.DS_Store
|
|
28
|
+
*.suo
|
|
29
|
+
*.ntvs*
|
|
30
|
+
*.njsproj
|
|
31
|
+
*.sln
|
|
32
|
+
*.sw?
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
24
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<!-- Root-relative: Vite prepends `base` at build time, so public/favicon.svg
|
|
7
|
+
is served at __BASE_PATH_SLASH__favicon.svg. Writing the base here
|
|
8
|
+
yourself would double it. -->
|
|
9
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
10
|
+
<!-- Overwritten at runtime: procode-lowcode-core's Screen sets
|
|
11
|
+
document.title from the rendered schema's screenName. -->
|
|
12
|
+
<title>__APP_NAME__</title>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<div id="root" style="height: 100dvh"></div>
|
|
16
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PKG_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"homepage": "__BASE_PATH_SLASH__",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"build": "tsc && vite build",
|
|
10
|
+
"preview": "vite preview",
|
|
11
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {},
|
|
14
|
+
"devDependencies": {}
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SampleContacts } from "./SampleContacts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extension point 2 of 5: screenId -> adapter.
|
|
5
|
+
*
|
|
6
|
+
* Keyed by the `screenId` INSIDE the schema JSON — NOT by the slug. Read the id
|
|
7
|
+
* out of the file; never guess it.
|
|
8
|
+
*
|
|
9
|
+
* main.tsx registers this function onto window as `getAdapter__APP_CODE__`, and
|
|
10
|
+
* the core resolves that global by name from VITE_APPLICATION_CODE. If the two
|
|
11
|
+
* ever diverge, EVERY adapter here silently falls back to a dummy and no screen
|
|
12
|
+
* has data — on every screen at once, with nothing in the console.
|
|
13
|
+
*/
|
|
14
|
+
export const getAdapter = (screenId: string): any => {
|
|
15
|
+
switch (screenId) {
|
|
16
|
+
case "sample-contacts":
|
|
17
|
+
return new SampleContacts();
|
|
18
|
+
default:
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|