create-reactaform-app 0.1.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 +125 -0
- package/bin/cli.js +36 -0
- package/package.json +35 -0
- package/src/createApp.js +269 -0
- package/src/templates/common/formDefinition.js +24 -0
- package/src/templates/examples/contact-form/js/formDefinition.js +31 -0
- package/src/templates/next/app/globals.css +7 -0
- package/src/templates/next/app/layout.jsx +13 -0
- package/src/templates/next/app/page.jsx +11 -0
- package/src/templates/next-ts/app/globals.css +7 -0
- package/src/templates/next-ts/app/layout.tsx +13 -0
- package/src/templates/next-ts/app/page.tsx +11 -0
- package/src/templates/vite/App.jsx +13 -0
- package/src/templates/vite-ts/App.tsx +13 -0
- package/src/utils/exec.js +66 -0
- package/src/utils/fs.js +18 -0
- package/src/utils/log.js +5 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# create-reactaform-app
|
|
2
|
+
|
|
3
|
+
Scaffold a minimal Vite (default) or Next.js React app with ReactaForm preconfigured. Paste a form definition and see results instantly.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
Create a new app from the CLI (JavaScript):
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx create-reactaform-app my-app
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
TypeScript project:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx create-reactaform-app my-app --ts
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Create a Next.js app (App Router):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx create-reactaform-app my-next-app --next
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Use an example overlay (overwrites the starter `formDefinition`):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx create-reactaform-app my-app --ts --example contact-form
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
After creating the project:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd my-app
|
|
34
|
+
npm install
|
|
35
|
+
npm run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Open the URL shown by the dev server (usually http://localhost:5173 or http://localhost:3000).
|
|
39
|
+
|
|
40
|
+
## What you get
|
|
41
|
+
|
|
42
|
+
- Vite + React app or Next.js App Router scaffolded
|
|
43
|
+
- `reactaform` installed and wired into the starter app (`src/App.jsx` or `app/page`)
|
|
44
|
+
- A starter `formDefinition` file you can edit to update the UI instantly
|
|
45
|
+
|
|
46
|
+
## Project layout (Vite)
|
|
47
|
+
|
|
48
|
+
my-app/
|
|
49
|
+
- src/
|
|
50
|
+
- App.jsx # ReactaForm renderer
|
|
51
|
+
- formDefinition.js
|
|
52
|
+
- main.jsx
|
|
53
|
+
- index.html
|
|
54
|
+
- package.json
|
|
55
|
+
- vite.config.js
|
|
56
|
+
|
|
57
|
+
## Project layout (Next.js App Router)
|
|
58
|
+
|
|
59
|
+
my-next-app/
|
|
60
|
+
- app/
|
|
61
|
+
- page.jsx # imports `./formDefinition`
|
|
62
|
+
- formDefinition.js
|
|
63
|
+
- package.json
|
|
64
|
+
|
|
65
|
+
## How it works
|
|
66
|
+
|
|
67
|
+
1. CLI scaffolds a base project (Vite or Next) using the official initializers.
|
|
68
|
+
2. The CLI copies small template files into the generated project (App wiring, styles).
|
|
69
|
+
3. The CLI creates a starter `formDefinition.js` (or `.ts` when `--ts`) inside the app so `ReactaForm` has data to render.
|
|
70
|
+
4. If `--example <name>` is provided, the example files are overlaid into the project (JS examples are converted to TypeScript when `--ts`).
|
|
71
|
+
|
|
72
|
+
## Starter `formDefinition` example
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// src/formDefinition.js
|
|
76
|
+
export const formDefinition = {
|
|
77
|
+
name: 'contactForm',
|
|
78
|
+
displayName: 'Contact Form',
|
|
79
|
+
version: '1.0.0',
|
|
80
|
+
properties: [
|
|
81
|
+
{ name: 'fullName', displayName: 'Full Name', type: 'text', defaultValue: '', required: true },
|
|
82
|
+
{ name: 'email', displayName: 'Email', type: 'email', defaultValue: '', required: true }
|
|
83
|
+
]
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## CLI options
|
|
88
|
+
|
|
89
|
+
- `--ts` Create a TypeScript project
|
|
90
|
+
- `--next` Create a Next.js App Router project
|
|
91
|
+
- `--install` Run `npm install` after scaffolding
|
|
92
|
+
- `--example` Overlay an example (e.g. `--example contact-form`)
|
|
93
|
+
|
|
94
|
+
## Testing the CLI locally
|
|
95
|
+
|
|
96
|
+
Pack and test without publishing:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm pack
|
|
100
|
+
npx ./create-reactaform-app-0.1.0.tgz my-app-test --ts --example contact-form
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Or link locally for iterative testing:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm link
|
|
107
|
+
create-reactaform-app my-app-test --ts --example contact-form
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Publishing notes
|
|
111
|
+
|
|
112
|
+
- Ensure `bin/cli.js` is executable and has the Node shebang (`#!/usr/bin/env node`).
|
|
113
|
+
- Add `repository`, `author`, `files`, and `publishConfig` to `package.json` before publishing.
|
|
114
|
+
|
|
115
|
+
## Requirements
|
|
116
|
+
|
|
117
|
+
- Node.js >= 18
|
|
118
|
+
|
|
119
|
+
## Contributing
|
|
120
|
+
|
|
121
|
+
Contributions welcome: open an issue or PR with improvements, examples, or bugfixes.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createApp } from '../src/createApp.js';
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const name = args.find(arg => !arg.startsWith('-'));
|
|
6
|
+
|
|
7
|
+
if (!name) {
|
|
8
|
+
console.error('❌ Please provide a project name.');
|
|
9
|
+
console.log('Usage: npx create-reactaform-app my-app');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const options = {
|
|
14
|
+
ts: args.includes('--ts'),
|
|
15
|
+
next: args.includes('--next'),
|
|
16
|
+
install: args.includes('--install'),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// parse --example value (support `--example name` and `--example=name`)
|
|
20
|
+
const exampleArg = args.find(a => a === '--example' || a.startsWith('--example='));
|
|
21
|
+
if (exampleArg) {
|
|
22
|
+
if (exampleArg.includes('=')) {
|
|
23
|
+
options.example = exampleArg.split('=')[1];
|
|
24
|
+
} else {
|
|
25
|
+
const idx = args.indexOf('--example');
|
|
26
|
+
if (idx >= 0 && idx < args.length - 1 && !args[idx + 1].startsWith('-')) {
|
|
27
|
+
options.example = args[idx + 1];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
createApp(name, options).catch(err => {
|
|
33
|
+
console.error('❌ Failed to create app');
|
|
34
|
+
console.error(err);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-reactaform-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a Vite/Next.js + React app with ReactaForm preconfigured",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-reactaform-app": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["vite", "react", "ReactaForm", "cli"],
|
|
13
|
+
"author": "yanggmtl <yanggmtl@gmail.com>",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/yanggmtl/create-reactaform-app.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/yanggmtl/create-reactaform-app/issues"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/yanggmtl/create-reactaform-app#readme",
|
|
22
|
+
"files": [
|
|
23
|
+
"bin/",
|
|
24
|
+
"src/",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "echo \"No tests specified\" && exit 0",
|
|
32
|
+
"lint": "echo \"No lint configured\" && exit 0"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|
package/src/createApp.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { exec } from "./utils/exec.js";
|
|
4
|
+
import { copyDir } from "./utils/fs.js";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import * as _logModule from "./utils/log.js";
|
|
7
|
+
const log = _logModule.log ??
|
|
8
|
+
_logModule.default ?? {
|
|
9
|
+
info: (msg) => console.log(msg),
|
|
10
|
+
success: (msg) => console.log(msg),
|
|
11
|
+
error: (msg) => console.error(msg),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
export async function createApp(appName, options) {
|
|
17
|
+
// `scaffoldTemplate` is the template name passed to `create-vite` (use Vite's
|
|
18
|
+
// official React templates: `react` / `react-ts`).
|
|
19
|
+
// `template` is the local folder name under `src/templates` that we'll copy
|
|
20
|
+
// into the generated project (we keep `vite` / `vite-ts` here).
|
|
21
|
+
const scaffoldTemplate = options.next
|
|
22
|
+
? options.ts
|
|
23
|
+
? "next-ts"
|
|
24
|
+
: "next"
|
|
25
|
+
: options.ts
|
|
26
|
+
? "react-ts"
|
|
27
|
+
: "react";
|
|
28
|
+
const template = options.next
|
|
29
|
+
? options.ts
|
|
30
|
+
? "next-ts"
|
|
31
|
+
: "next"
|
|
32
|
+
: options.ts
|
|
33
|
+
? "vite-ts"
|
|
34
|
+
: "vite";
|
|
35
|
+
|
|
36
|
+
const creatingLabel = options.next ? "Next.js" : "ReactaForm";
|
|
37
|
+
log.info(`Creating ${creatingLabel} app: ${appName}`);
|
|
38
|
+
|
|
39
|
+
// 1. Scaffold Vite/Next.js (try primary command, then a fallback for environments
|
|
40
|
+
// where `npm create` may fail)
|
|
41
|
+
// Run scaffold commands with CI=true to force non-interactive defaults
|
|
42
|
+
const spawnEnv = { ...process.env, CI: "true" };
|
|
43
|
+
|
|
44
|
+
const extraFlags = ["--yes"];
|
|
45
|
+
|
|
46
|
+
if (options.next) {
|
|
47
|
+
// Scaffold Next.js app (try npm create, fallback to npx)
|
|
48
|
+
const nextArgsBase = ["create", "next-app@latest", appName];
|
|
49
|
+
// add flags after a -- so they're passed to the initializer
|
|
50
|
+
const nextArgsTS = options.ts
|
|
51
|
+
? ["--", "--ts", "--use-npm"]
|
|
52
|
+
: ["--", "--use-npm"];
|
|
53
|
+
try {
|
|
54
|
+
await exec("npm", [...nextArgsBase, ...nextArgsTS, ...extraFlags], {
|
|
55
|
+
env: spawnEnv,
|
|
56
|
+
capture: true,
|
|
57
|
+
});
|
|
58
|
+
} catch (err) {
|
|
59
|
+
try {
|
|
60
|
+
await exec(
|
|
61
|
+
"npx",
|
|
62
|
+
["create-next-app@latest", appName, ...nextArgsTS, ...extraFlags],
|
|
63
|
+
{ env: spawnEnv, capture: true },
|
|
64
|
+
);
|
|
65
|
+
} catch (err2) {
|
|
66
|
+
// give up and rethrow
|
|
67
|
+
throw err2;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// Vite React scaffolding (existing behavior)
|
|
72
|
+
try {
|
|
73
|
+
await exec(
|
|
74
|
+
"npm",
|
|
75
|
+
[
|
|
76
|
+
"create",
|
|
77
|
+
"vite@latest",
|
|
78
|
+
appName,
|
|
79
|
+
"--",
|
|
80
|
+
"--template",
|
|
81
|
+
scaffoldTemplate,
|
|
82
|
+
...extraFlags,
|
|
83
|
+
],
|
|
84
|
+
{ env: spawnEnv, capture: true },
|
|
85
|
+
);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
// Fallback: try `npm init` or `npx create-vite` style invocation
|
|
88
|
+
try {
|
|
89
|
+
await exec(
|
|
90
|
+
"npm",
|
|
91
|
+
[
|
|
92
|
+
"init",
|
|
93
|
+
"vite@latest",
|
|
94
|
+
appName,
|
|
95
|
+
"--",
|
|
96
|
+
"--template",
|
|
97
|
+
scaffoldTemplate,
|
|
98
|
+
...extraFlags,
|
|
99
|
+
],
|
|
100
|
+
{ env: spawnEnv, capture: true },
|
|
101
|
+
);
|
|
102
|
+
} catch (err2) {
|
|
103
|
+
// final fallback: npx
|
|
104
|
+
await exec(
|
|
105
|
+
"npx",
|
|
106
|
+
[
|
|
107
|
+
"create-vite@latest",
|
|
108
|
+
appName,
|
|
109
|
+
"--",
|
|
110
|
+
"--template",
|
|
111
|
+
scaffoldTemplate,
|
|
112
|
+
...extraFlags,
|
|
113
|
+
],
|
|
114
|
+
{ env: spawnEnv, capture: true },
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const appDir = path.resolve(process.cwd(), appName);
|
|
121
|
+
|
|
122
|
+
// 2. Install ReactaForm
|
|
123
|
+
await exec("npm", ["install", "reactaform"], { cwd: appDir });
|
|
124
|
+
|
|
125
|
+
// 3. Copy templates (for Next copy to project root, for React copy to /src)
|
|
126
|
+
const templateDir = path.join(__dirname, "templates", template);
|
|
127
|
+
const targetDir = options.next ? appDir : path.join(appDir, "src");
|
|
128
|
+
|
|
129
|
+
await copyDir(templateDir, targetDir);
|
|
130
|
+
|
|
131
|
+
// For Next apps we write `formDefinition.*` inside `app/`.
|
|
132
|
+
if (options.next) {
|
|
133
|
+
try {
|
|
134
|
+
const rootJs = path.join(appDir, "formDefinition.js");
|
|
135
|
+
const rootTs = path.join(appDir, "formDefinition.ts");
|
|
136
|
+
await fs.unlink(rootJs).catch(() => {});
|
|
137
|
+
await fs.unlink(rootTs).catch(() => {});
|
|
138
|
+
} catch (e) {
|
|
139
|
+
// ignore
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Copy shared formDefinition into the new project. For Next apps place it
|
|
144
|
+
// inside the `app/` folder so pages can import `./formDefinition`.
|
|
145
|
+
// Determine where to write app-local files (Next -> app/, React -> src)
|
|
146
|
+
const writeDir = options.next ? path.join(appDir, "app") : targetDir;
|
|
147
|
+
await fs.mkdir(writeDir, { recursive: true });
|
|
148
|
+
|
|
149
|
+
// If an `--example` was requested, copy example files first so they
|
|
150
|
+
// overlay template defaults. Examples live under
|
|
151
|
+
// `src/templates/examples/<example>/js` (we only keep JS examples).
|
|
152
|
+
if (options.example) {
|
|
153
|
+
try {
|
|
154
|
+
const exampleDirJs = path.join(
|
|
155
|
+
__dirname,
|
|
156
|
+
"templates",
|
|
157
|
+
"examples",
|
|
158
|
+
options.example,
|
|
159
|
+
"js",
|
|
160
|
+
);
|
|
161
|
+
await copyDir(exampleDirJs, writeDir);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
// ignore if example folder doesn't exist
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Choose source formDefinition: prefer example's JS file when present,
|
|
168
|
+
// otherwise fall back to the shared common file. Then write it as .js or
|
|
169
|
+
// convert to .ts if requested.
|
|
170
|
+
try {
|
|
171
|
+
const commonFormPath = path.join(
|
|
172
|
+
__dirname,
|
|
173
|
+
"templates",
|
|
174
|
+
"common",
|
|
175
|
+
"formDefinition.js",
|
|
176
|
+
);
|
|
177
|
+
const exampleFormPath = options.example
|
|
178
|
+
? path.join(
|
|
179
|
+
__dirname,
|
|
180
|
+
"templates",
|
|
181
|
+
"examples",
|
|
182
|
+
options.example,
|
|
183
|
+
"js",
|
|
184
|
+
"formDefinition.js",
|
|
185
|
+
)
|
|
186
|
+
: null;
|
|
187
|
+
|
|
188
|
+
let sourcePath = commonFormPath;
|
|
189
|
+
if (exampleFormPath) {
|
|
190
|
+
try {
|
|
191
|
+
await fs.access(exampleFormPath);
|
|
192
|
+
sourcePath = exampleFormPath;
|
|
193
|
+
} catch (e) {
|
|
194
|
+
// example form not present, keep common
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let formData = await fs.readFile(sourcePath, "utf8");
|
|
199
|
+
const outName = options.ts ? "formDefinition.ts" : "formDefinition.js";
|
|
200
|
+
if (options.ts) {
|
|
201
|
+
formData = formData.replace(
|
|
202
|
+
/export const formDefinition\s*=\s*/m,
|
|
203
|
+
"export const formDefinition: Record<string, unknown> = ",
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await fs.writeFile(path.join(writeDir, outName), formData, "utf8");
|
|
208
|
+
|
|
209
|
+
// If we wrote a TS file for a previously-copied example, remove the JS
|
|
210
|
+
// variant so project has the correct extension.
|
|
211
|
+
if (options.ts) {
|
|
212
|
+
try {
|
|
213
|
+
await fs.unlink(path.join(writeDir, "formDefinition.js"));
|
|
214
|
+
} catch (e) {
|
|
215
|
+
// ignore if not present
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch (e) {
|
|
219
|
+
// fail silently if no source formDefinition found
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// If user requested a JS Next app (no --ts), remove any TypeScript files
|
|
223
|
+
// that might exist in the generated project (safety cleanup).
|
|
224
|
+
if (options.next && !options.ts) {
|
|
225
|
+
async function removeTsFiles(dir) {
|
|
226
|
+
let entries;
|
|
227
|
+
try {
|
|
228
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
229
|
+
} catch (err) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const entry of entries) {
|
|
234
|
+
const entryPath = path.join(dir, entry.name);
|
|
235
|
+
if (entry.isDirectory()) {
|
|
236
|
+
await removeTsFiles(entryPath);
|
|
237
|
+
} else {
|
|
238
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
239
|
+
if (
|
|
240
|
+
ext === ".ts" ||
|
|
241
|
+
ext === ".tsx" ||
|
|
242
|
+
entry.name === "tsconfig.json" ||
|
|
243
|
+
entry.name === "next-env.d.ts"
|
|
244
|
+
) {
|
|
245
|
+
try {
|
|
246
|
+
await fs.unlink(entryPath);
|
|
247
|
+
} catch (e) {
|
|
248
|
+
// ignore errors
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await removeTsFiles(appDir);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 4. Optional full install
|
|
259
|
+
if (options.install) {
|
|
260
|
+
await exec("npm", ["install"], { cwd: appDir });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
log.success("Done!");
|
|
264
|
+
log.info(`Next steps:
|
|
265
|
+
cd ${appName}
|
|
266
|
+
npm install
|
|
267
|
+
npm run dev
|
|
268
|
+
`);
|
|
269
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared example ReactaForm definition.
|
|
3
|
+
*/
|
|
4
|
+
export const formDefinition = {
|
|
5
|
+
name: 'contactForm',
|
|
6
|
+
displayName: 'Contact Form',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
properties: [
|
|
9
|
+
{
|
|
10
|
+
name: 'fullName',
|
|
11
|
+
displayName: 'Full Name',
|
|
12
|
+
type: 'text',
|
|
13
|
+
defaultValue: '',
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'email',
|
|
18
|
+
displayName: 'Email',
|
|
19
|
+
type: 'email',
|
|
20
|
+
defaultValue: '',
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact form example (JavaScript)
|
|
3
|
+
*/
|
|
4
|
+
export const formDefinition = {
|
|
5
|
+
name: 'contactForm',
|
|
6
|
+
displayName: 'Contact Form (Example)',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
properties: [
|
|
9
|
+
{
|
|
10
|
+
name: 'fullName',
|
|
11
|
+
displayName: 'Full Name',
|
|
12
|
+
type: 'text',
|
|
13
|
+
defaultValue: '',
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'email',
|
|
18
|
+
displayName: 'Email',
|
|
19
|
+
type: 'email',
|
|
20
|
+
defaultValue: '',
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'message',
|
|
25
|
+
displayName: 'Message',
|
|
26
|
+
type: 'multiline',
|
|
27
|
+
defaultValue: '',
|
|
28
|
+
required: false,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { ReactaForm } from 'reactaform';
|
|
3
|
+
import { formDefinition } from './formDefinition';
|
|
4
|
+
|
|
5
|
+
export default function Page() {
|
|
6
|
+
return (
|
|
7
|
+
<div style={{ padding: 24, maxWidth: 600 , flex: '1', margin: '0 auto'}}>
|
|
8
|
+
<ReactaForm definitionData={formDefinition} />
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import './globals.css';
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
title: 'ReactaForm Next App',
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
8
|
+
return (
|
|
9
|
+
<html lang="en">
|
|
10
|
+
<body>{children}</body>
|
|
11
|
+
</html>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { ReactaForm } from 'reactaform';
|
|
3
|
+
import { formDefinition } from './formDefinition';
|
|
4
|
+
|
|
5
|
+
export default function Page() {
|
|
6
|
+
return (
|
|
7
|
+
<div style={{ padding: 24, maxWidth: 600 , flex: '1', margin: '0 auto'}}>
|
|
8
|
+
<ReactaForm definitionData={formDefinition} />
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ReactaForm } from 'reactaform';
|
|
2
|
+
import { formDefinition } from './formDefinition';
|
|
3
|
+
import './App.css';
|
|
4
|
+
|
|
5
|
+
export default function App() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="App" style={{ padding: 24, maxWidth: 600 }}>
|
|
8
|
+
<ReactaForm
|
|
9
|
+
definitionData={formDefinition}
|
|
10
|
+
/>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ReactaForm } from 'reactaform';
|
|
2
|
+
import { formDefinition } from './formDefinition';
|
|
3
|
+
import './App.css';
|
|
4
|
+
|
|
5
|
+
export default function App() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="App" style={{ padding: 24, maxWidth: 600 }}>
|
|
8
|
+
<ReactaForm
|
|
9
|
+
definitionData={formDefinition}
|
|
10
|
+
/>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
function isPromptLine(line) {
|
|
4
|
+
if (!line) return true;
|
|
5
|
+
// Filter box-drawing lines and create-vite prompt markers
|
|
6
|
+
if (/^[\s│└┌┐├┤┬┴─]+$/.test(line)) return true;
|
|
7
|
+
if (line.includes('◇')) return true;
|
|
8
|
+
if (/Use rolldown-vite/i.test(line)) return true;
|
|
9
|
+
if (/Install with npm and start now/i.test(line)) return true;
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function exec(command, args, options = {}) {
|
|
14
|
+
const capture = Boolean(options.capture);
|
|
15
|
+
const spawnOpts = {
|
|
16
|
+
shell: process.platform === 'win32',
|
|
17
|
+
...options,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// When capturing, do not set stdio: 'inherit' so we can filter output
|
|
21
|
+
if (!capture) spawnOpts.stdio = 'inherit';
|
|
22
|
+
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const child = spawn(command, args, spawnOpts);
|
|
25
|
+
|
|
26
|
+
if (capture) {
|
|
27
|
+
if (child.stdout) {
|
|
28
|
+
let stdoutBuf = '';
|
|
29
|
+
child.stdout.on('data', chunk => {
|
|
30
|
+
stdoutBuf += chunk.toString();
|
|
31
|
+
const parts = stdoutBuf.split(/\r?\n/);
|
|
32
|
+
stdoutBuf = parts.pop();
|
|
33
|
+
for (const line of parts) {
|
|
34
|
+
if (!isPromptLine(line)) process.stdout.write(line + '\n');
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
child.stdout.on('end', () => {
|
|
38
|
+
if (stdoutBuf && !isPromptLine(stdoutBuf)) process.stdout.write(stdoutBuf + '\n');
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (child.stderr) {
|
|
43
|
+
let stderrBuf = '';
|
|
44
|
+
child.stderr.on('data', chunk => {
|
|
45
|
+
stderrBuf += chunk.toString();
|
|
46
|
+
const parts = stderrBuf.split(/\r?\n/);
|
|
47
|
+
stderrBuf = parts.pop();
|
|
48
|
+
for (const line of parts) {
|
|
49
|
+
if (!isPromptLine(line)) process.stderr.write(line + '\n');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
child.stderr.on('end', () => {
|
|
53
|
+
if (stderrBuf && !isPromptLine(stderrBuf)) process.stderr.write(stderrBuf + '\n');
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
child.on('close', code => {
|
|
59
|
+
if (code !== 0) {
|
|
60
|
+
reject(new Error(`${command} exited with code ${code}`));
|
|
61
|
+
} else {
|
|
62
|
+
resolve();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
package/src/utils/fs.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export async function copyDir(src, dest) {
|
|
5
|
+
await fs.mkdir(dest, { recursive: true });
|
|
6
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
7
|
+
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
const srcPath = path.join(src, entry.name);
|
|
10
|
+
const destPath = path.join(dest, entry.name);
|
|
11
|
+
|
|
12
|
+
if (entry.isDirectory()) {
|
|
13
|
+
await copyDir(srcPath, destPath);
|
|
14
|
+
} else {
|
|
15
|
+
await fs.copyFile(srcPath, destPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|