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.
Files changed (54) hide show
  1. package/README.md +240 -2
  2. package/bin/create-procode.mjs +9 -0
  3. package/package.json +30 -4
  4. package/src/args.mjs +139 -0
  5. package/src/cli.mjs +392 -0
  6. package/src/deps.mjs +195 -0
  7. package/src/log.mjs +23 -0
  8. package/src/prompt.mjs +60 -0
  9. package/src/scaffold.mjs +104 -0
  10. package/templates/base/README.md.tmpl +183 -0
  11. package/templates/base/_dotenv.local.example.tmpl +8 -0
  12. package/templates/base/_dotenv.tmpl +41 -0
  13. package/templates/base/_doteslintrc.cjs.tmpl +125 -0
  14. package/templates/base/_dotgitignore.tmpl +32 -0
  15. package/templates/base/_dotnvmrc.tmpl +1 -0
  16. package/templates/base/_dotvscode/extensions.json +3 -0
  17. package/templates/base/index.html.tmpl +18 -0
  18. package/templates/base/package.json.tmpl +15 -0
  19. package/templates/base/public/favicon.svg +4 -0
  20. package/templates/base/src/AdapterProvider/AdapterFactory.ts.tmpl +21 -0
  21. package/templates/base/src/AdapterProvider/SampleContacts.ts.tmpl +136 -0
  22. package/templates/base/src/CustomAction.ts.tmpl +47 -0
  23. package/templates/base/src/CustomAuthorizationProvider.ts.tmpl +77 -0
  24. package/templates/base/src/Factory/Actions/myAction.ts.tmpl +51 -0
  25. package/templates/base/src/Factory/Actions/sampleContacts.ts.tmpl +244 -0
  26. package/templates/base/src/Factory/Actions/setField.ts.tmpl +20 -0
  27. package/templates/base/src/Factory/CustomRuleEffectFactory.ts.tmpl +30 -0
  28. package/templates/base/src/Factory/CustomValidationFactory.tsx.tmpl +24 -0
  29. package/templates/base/src/Factory/CustomWidgetFactory.tsx.tmpl +43 -0
  30. package/templates/base/src/Factory/RuleEffects/readOnlyEffect.ts.tmpl +27 -0
  31. package/templates/base/src/Factory/Validations/myValidation.ts.tmpl +52 -0
  32. package/templates/base/src/Factory/Widgets/MyWidget/index.tsx.tmpl +101 -0
  33. package/templates/base/src/Factory/Widgets/Notification/index.tsx.tmpl +53 -0
  34. package/templates/base/src/Factory/Widgets/Notification/notifiFunc.ts.tmpl +9 -0
  35. package/templates/base/src/Factory/Widgets/SectionNav/index.tsx +193 -0
  36. package/templates/base/src/Factory/Widgets/SectionNav/style.scss +139 -0
  37. package/templates/base/src/Module/Sample/Contacts.json.tmpl +965 -0
  38. package/templates/base/src/Module/index.ts.tmpl +26 -0
  39. package/templates/base/src/Screens/index.tsx.tmpl +45 -0
  40. package/templates/base/src/Utils/errorMessages.ts.tmpl +35 -0
  41. package/templates/base/src/Utils/fieldErrorMapper.ts.tmpl +46 -0
  42. package/templates/base/src/Utils/logger.ts.tmpl +21 -0
  43. package/templates/base/src/Utils/responseFormatter.ts.tmpl +39 -0
  44. package/templates/base/src/Utils/storages.ts.tmpl +35 -0
  45. package/templates/base/src/assets/styles/index.scss.tmpl +146 -0
  46. package/templates/base/src/constant.ts.tmpl +53 -0
  47. package/templates/base/src/icons.ts.tmpl +17 -0
  48. package/templates/base/src/localizationResolver.ts.tmpl +48 -0
  49. package/templates/base/src/main.tsx.tmpl +136 -0
  50. package/templates/base/src/routes.ts.tmpl +20 -0
  51. package/templates/base/src/vite-env.d.ts.tmpl +20 -0
  52. package/templates/base/tsconfig.json.tmpl +30 -0
  53. package/templates/base/tsconfig.node.json.tmpl +11 -0
  54. package/templates/base/vite.config.ts.tmpl +61 -0
@@ -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,3 @@
1
+ {
2
+ "recommendations": ["dbaeumer.vscode-eslint"]
3
+ }
@@ -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,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="7" fill="#1261e8"/>
3
+ <path d="M11 24V8h6.2c3.6 0 5.8 2 5.8 5.1s-2.2 5.1-5.8 5.1H14.4V24z M14.4 15.3h2.6c1.7 0 2.6-.8 2.6-2.2s-.9-2.2-2.6-2.2h-2.6z" fill="#fff"/>
4
+ </svg>
@@ -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
+ };