create-kdna-web-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/LICENSE +17 -0
- package/NOTICE +13 -0
- package/README.md +140 -0
- package/SECURITY.md +38 -0
- package/bin/create-kdna-web-app.js +9 -0
- package/docs/getting-started.md +67 -0
- package/docs/template-checklist.md +30 -0
- package/docs/templates.md +89 -0
- package/package.json +45 -0
- package/src/scaffold.js +168 -0
- package/templates/express/.env.example +2 -0
- package/templates/express/_gitignore +2 -0
- package/templates/express/package.json +15 -0
- package/templates/express/public/index.html +29 -0
- package/templates/express/scripts/smoke.mjs +7 -0
- package/templates/express/src/server.js +15 -0
- package/templates/nextjs/.env.local.example +2 -0
- package/templates/nextjs/_gitignore +3 -0
- package/templates/nextjs/app/api/kdna/[...route]/route.js +8 -0
- package/templates/nextjs/app/page.jsx +42 -0
- package/templates/nextjs/package.json +19 -0
- package/templates/nextjs/scripts/smoke.mjs +21 -0
- package/templates/nextjs-pages/.env.local.example +2 -0
- package/templates/nextjs-pages/_gitignore +3 -0
- package/templates/nextjs-pages/package.json +19 -0
- package/templates/nextjs-pages/pages/api/kdna/[...route].js +17 -0
- package/templates/nextjs-pages/pages/index.jsx +40 -0
- package/templates/nextjs-pages/scripts/smoke.mjs +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 KDNA Authors
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
create-kdna-web-app
|
|
2
|
+
Copyright 2026 KDNA Authors
|
|
3
|
+
|
|
4
|
+
This product includes software developed at
|
|
5
|
+
https://github.com/aikdna/create-kdna-web-app
|
|
6
|
+
|
|
7
|
+
create-kdna-web-app is a project scaffolding CLI for KDNA-integrated
|
|
8
|
+
web applications. Generated projects include the KDNA packages each
|
|
9
|
+
template imports at runtime, pre-configured with a working validate →
|
|
10
|
+
plan-load → load → consume flow.
|
|
11
|
+
|
|
12
|
+
See LICENSE for the full Apache 2.0 license text.
|
|
13
|
+
See README.md for usage and template options.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# create-kdna-web-app
|
|
2
|
+
|
|
3
|
+
**Scaffold a KDNA-integrated web application in one command.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx create-kdna-web-app my-app
|
|
7
|
+
cd my-app
|
|
8
|
+
npm run dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The Next.js templates include `@aikdna/kdna-web-server` and
|
|
12
|
+
`@aikdna/kdna-react` pre-configured with a validate -> inspect ->
|
|
13
|
+
plan-load -> load demo. The Express template includes the server adapter
|
|
14
|
+
and a minimal static HTML demo.
|
|
15
|
+
|
|
16
|
+
[](https://www.npmjs.com/package/create-kdna-web-app)
|
|
17
|
+
[](LICENSE)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx create-kdna-web-app <project-name> [options]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Options
|
|
28
|
+
|
|
29
|
+
| Flag | Default | Description |
|
|
30
|
+
|------|---------|-------------|
|
|
31
|
+
| `--template` | `nextjs` | Project template — see [templates](#templates) |
|
|
32
|
+
| `--package-manager` | auto-detected | `npm`, `pnpm`, or `yarn` |
|
|
33
|
+
| `--no-install` | — | Scaffold files without running install |
|
|
34
|
+
|
|
35
|
+
### Examples
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Next.js App Router (default)
|
|
39
|
+
npx create-kdna-web-app my-app
|
|
40
|
+
|
|
41
|
+
# Express
|
|
42
|
+
npx create-kdna-web-app my-app --template express
|
|
43
|
+
|
|
44
|
+
# Next.js Pages Router
|
|
45
|
+
npx create-kdna-web-app my-app --template nextjs-pages
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Templates
|
|
51
|
+
|
|
52
|
+
### `nextjs` (default)
|
|
53
|
+
|
|
54
|
+
- Next.js 16+ with App Router
|
|
55
|
+
- `app/api/kdna/[...route]/route.js` — all KDNA endpoints mounted
|
|
56
|
+
- `app/page.jsx` — full demo: file drop, inspect, load, display
|
|
57
|
+
- `.env.local.example` for local configuration
|
|
58
|
+
|
|
59
|
+
### `nextjs-pages`
|
|
60
|
+
|
|
61
|
+
- Next.js with Pages Router
|
|
62
|
+
- `pages/api/kdna/[...route].js` — all KDNA endpoints
|
|
63
|
+
- `pages/index.jsx` — same demo as the App Router template
|
|
64
|
+
|
|
65
|
+
### `express`
|
|
66
|
+
|
|
67
|
+
- Express with ESM
|
|
68
|
+
- `src/server.js` — KDNA router mounted at `/api/kdna`
|
|
69
|
+
- `public/index.html` — minimal HTML demo page
|
|
70
|
+
- `.env.example` for local configuration
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Template dependencies
|
|
75
|
+
|
|
76
|
+
| Template | KDNA packages |
|
|
77
|
+
|----------|---------------|
|
|
78
|
+
| `nextjs` | `@aikdna/kdna-core`, `@aikdna/kdna-web-server`, `@aikdna/kdna-react` |
|
|
79
|
+
| `nextjs-pages` | `@aikdna/kdna-core`, `@aikdna/kdna-web-server`, `@aikdna/kdna-react` |
|
|
80
|
+
| `express` | `@aikdna/kdna-core`, `@aikdna/kdna-web-server` |
|
|
81
|
+
|
|
82
|
+
## Pre-configured flow
|
|
83
|
+
|
|
84
|
+
The Next.js templates provide the full React flow:
|
|
85
|
+
|
|
86
|
+
1. User drops a `.kdna` file onto the `<KDNAFileDropzone>`.
|
|
87
|
+
2. The file is uploaded and inspected — metadata appears immediately.
|
|
88
|
+
3. `<KDNALoadPlanGate>` checks whether a password or license is needed.
|
|
89
|
+
4. If the asset is open, it loads automatically.
|
|
90
|
+
5. If the asset requires a password, `<KDNAPasswordUnlockDialog>` appears.
|
|
91
|
+
6. The loaded content is displayed.
|
|
92
|
+
|
|
93
|
+
The Express template mounts the same `/api/kdna` server endpoints and includes
|
|
94
|
+
a minimal HTML page that uploads, inspects, and loads an open `.kdna` asset.
|
|
95
|
+
|
|
96
|
+
### Environment variables
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Optional
|
|
100
|
+
KDNA_STORAGE_DIR=/tmp/kdna
|
|
101
|
+
|
|
102
|
+
# Optional — only needed for licensed-mode assets
|
|
103
|
+
KDNA_ACTIVATION_URL=https://your-activation-server.example.com
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## After scaffolding
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cd my-app
|
|
112
|
+
cp .env.local.example .env.local # Next.js templates
|
|
113
|
+
# or: cp .env.example .env # Express template, if you load env files locally
|
|
114
|
+
npm test # smoke-test KDNA package imports
|
|
115
|
+
npm run dev
|
|
116
|
+
# Open http://localhost:3000 and drop a .kdna file
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
To get a `.kdna` file for testing:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm install -g @aikdna/kdna-cli
|
|
123
|
+
curl -LO https://github.com/aikdna/kdna-assets/releases/download/agent-project-context-v0.1.2/agent-project-context-v0.1.2.kdna
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Related packages
|
|
129
|
+
|
|
130
|
+
| Package | Role |
|
|
131
|
+
|---------|------|
|
|
132
|
+
| [`@aikdna/kdna-core`](https://github.com/aikdna/kdna) | KDNA format and runtime |
|
|
133
|
+
| [`@aikdna/kdna-web-server`](https://github.com/aikdna/kdna-web-server) | Server-side adapter |
|
|
134
|
+
| [`@aikdna/kdna-react`](https://github.com/aikdna/kdna-react) | React components and hooks |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
Apache 2.0 — see [LICENSE](./LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
Please **do not** report security vulnerabilities through public GitHub issues.
|
|
6
|
+
|
|
7
|
+
Instead, use one of these private channels:
|
|
8
|
+
|
|
9
|
+
- **GitHub Private Vulnerability Reporting**: Go to the [Security Advisories](https://github.com/aikdna/create-kdna-web-app/security/advisories/new) page
|
|
10
|
+
- **Email**: security@aikdna.com
|
|
11
|
+
|
|
12
|
+
We aim to respond within 72 hours and provide a timeline for resolution within 1 week.
|
|
13
|
+
Please do not disclose the vulnerability publicly until we have had a chance to address it.
|
|
14
|
+
|
|
15
|
+
## Supported Versions
|
|
16
|
+
|
|
17
|
+
`create-kdna-web-app` is a pre-release scaffolder support surface. Until the
|
|
18
|
+
first stable package release, security support tracks the latest mainline
|
|
19
|
+
pre-release and the canonical KDNA protocol/runtime surfaces.
|
|
20
|
+
|
|
21
|
+
| Component | Supported Versions |
|
|
22
|
+
|-----------|-------------------|
|
|
23
|
+
| KDNA Protocol | Latest tagged release |
|
|
24
|
+
| kdna-cli | Latest minor release |
|
|
25
|
+
| create-kdna-web-app | Latest mainline pre-release |
|
|
26
|
+
|
|
27
|
+
Older pre-release versions may receive critical security patches on a
|
|
28
|
+
case-by-case basis.
|
|
29
|
+
|
|
30
|
+
## Security Model
|
|
31
|
+
|
|
32
|
+
`create-kdna-web-app` scaffolds KDNA-integrated web applications. Generated
|
|
33
|
+
templates must consume the public KDNA package contracts rather than define
|
|
34
|
+
protocol validity, access modes, LoadPlan states, or crypto policy.
|
|
35
|
+
|
|
36
|
+
For the KDNA Protocol security architecture, see
|
|
37
|
+
[GOVERNANCE.md](https://github.com/aikdna/kdna/blob/main/docs/GOVERNANCE.md)
|
|
38
|
+
in the main protocol repository.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
This page walks through the first five minutes after running
|
|
4
|
+
`npx create-kdna-web-app`.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Create a project
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx create-kdna-web-app my-app
|
|
12
|
+
cd my-app
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Configure environment variables
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cp .env.local.example .env.local
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Open `.env.local`. The only required variable is `KDNA_STORAGE_DIR`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
KDNA_STORAGE_DIR=/tmp/kdna
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`KDNA_ACTIVATION_URL` is optional. Leave it empty unless you are working
|
|
30
|
+
with licensed-mode assets and have a self-hosted activation server.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Start the development server
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run dev
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Open [http://localhost:3000](http://localhost:3000).
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Try it with a real .kdna file
|
|
45
|
+
|
|
46
|
+
Install the KDNA CLI and download an example asset:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g @aikdna/kdna-cli
|
|
50
|
+
|
|
51
|
+
curl -LO https://github.com/aikdna/kdna-assets/releases/download/agent-project-context-v0.1.2/agent-project-context-v0.1.2.kdna
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Drop the file onto the page. You should see:
|
|
55
|
+
|
|
56
|
+
1. The manifest metadata (domain, version, title).
|
|
57
|
+
2. A "Load" button or automatic loading for open assets.
|
|
58
|
+
3. The formatted payload content.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Next steps
|
|
63
|
+
|
|
64
|
+
- [Templates](./templates.md) — switch to Express or Pages Router
|
|
65
|
+
- [KDNA Core](https://github.com/aikdna/kdna) — understand the file format
|
|
66
|
+
- [kdna-web-server docs](https://github.com/aikdna/kdna-web-server) — add auth middleware, configure storage
|
|
67
|
+
- [kdna-react docs](https://github.com/aikdna/kdna-react) — customise the components
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Template checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist when adding or changing a `create-kdna-web-app`
|
|
4
|
+
template.
|
|
5
|
+
|
|
6
|
+
## Required files
|
|
7
|
+
|
|
8
|
+
- `package.json` with bounded dependency ranges, never `latest`.
|
|
9
|
+
- KDNA server route or server entrypoint.
|
|
10
|
+
- Browser demo page that exercises inspect, plan-load, and load.
|
|
11
|
+
- Environment example file for storage and activation settings.
|
|
12
|
+
- `scripts/smoke.mjs` that imports the KDNA package entrypoints used by
|
|
13
|
+
the generated project.
|
|
14
|
+
|
|
15
|
+
## Runtime boundaries
|
|
16
|
+
|
|
17
|
+
- KDNA assets stay server-side after upload.
|
|
18
|
+
- Decryption and license verification happen through
|
|
19
|
+
`@aikdna/kdna-web-server` or a compatible server API.
|
|
20
|
+
- Raw license keys go to activation endpoints, not `/load`.
|
|
21
|
+
- Remote-server configuration is not included until the server adapter
|
|
22
|
+
implements and documents that mode.
|
|
23
|
+
|
|
24
|
+
## Verification
|
|
25
|
+
|
|
26
|
+
- `npm test` passes in this repository.
|
|
27
|
+
- A generated project includes the expected route, page, env example, and
|
|
28
|
+
smoke script.
|
|
29
|
+
- The generated template package uses bounded dependency ranges.
|
|
30
|
+
- Public docs and package metadata mention only templates that exist.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Templates
|
|
2
|
+
|
|
3
|
+
`create-kdna-web-app` ships three templates. Choose with `--template`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## `nextjs` (default)
|
|
8
|
+
|
|
9
|
+
**When to use:** You are starting a new Next.js project or want the
|
|
10
|
+
most up-to-date template.
|
|
11
|
+
|
|
12
|
+
**Framework:** Next.js 14+ (App Router)
|
|
13
|
+
|
|
14
|
+
**What it includes:**
|
|
15
|
+
|
|
16
|
+
| File | Purpose |
|
|
17
|
+
|------|---------|
|
|
18
|
+
| `app/api/kdna/[...route]/route.js` | All KDNA endpoints (catch-all App Router route) |
|
|
19
|
+
| `app/page.jsx` | Demo page: file drop, inspect, load, display |
|
|
20
|
+
| `.env.local.example` | Environment variable template |
|
|
21
|
+
|
|
22
|
+
**Start:**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx create-kdna-web-app my-app --template nextjs
|
|
26
|
+
cd my-app && npm run dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## `nextjs-pages`
|
|
32
|
+
|
|
33
|
+
**When to use:** You are integrating into an existing Next.js Pages
|
|
34
|
+
Router project, or prefer the Pages Router.
|
|
35
|
+
|
|
36
|
+
**Framework:** Next.js (Pages Router)
|
|
37
|
+
|
|
38
|
+
**What it includes:**
|
|
39
|
+
|
|
40
|
+
| File | Purpose |
|
|
41
|
+
|------|---------|
|
|
42
|
+
| `pages/api/kdna/[...route].js` | All KDNA endpoints |
|
|
43
|
+
| `pages/index.jsx` | Demo page |
|
|
44
|
+
| `.env.local.example` | Environment variable template |
|
|
45
|
+
|
|
46
|
+
**Start:**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx create-kdna-web-app my-app --template nextjs-pages
|
|
50
|
+
cd my-app && npm run dev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## `express`
|
|
56
|
+
|
|
57
|
+
**When to use:** You need a standalone Node.js server, are not using
|
|
58
|
+
Next.js, or are integrating into an existing Express application.
|
|
59
|
+
|
|
60
|
+
**Framework:** Express (ESM)
|
|
61
|
+
|
|
62
|
+
**What it includes:**
|
|
63
|
+
|
|
64
|
+
| File | Purpose |
|
|
65
|
+
|------|---------|
|
|
66
|
+
| `src/server.js` | Express server with KDNA router at `/api/kdna` |
|
|
67
|
+
| `public/index.html` | Minimal HTML demo page |
|
|
68
|
+
| `.env.example` | Environment variable template |
|
|
69
|
+
|
|
70
|
+
**Start:**
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx create-kdna-web-app my-app --template express
|
|
74
|
+
cd my-app && npm start
|
|
75
|
+
# Open http://localhost:3000
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Choosing a template
|
|
81
|
+
|
|
82
|
+
| | `nextjs` | `nextjs-pages` | `express` |
|
|
83
|
+
|-|----------|----------------|-----------|
|
|
84
|
+
| KDNA API route included | Yes | Yes | Yes |
|
|
85
|
+
| Edge runtime (Vercel) | No* | No* | No |
|
|
86
|
+
| Browser demo included | React page | React page | Static HTML |
|
|
87
|
+
| React dependency included | Yes | Yes | No |
|
|
88
|
+
|
|
89
|
+
\* Use `@aikdna/kdna-web-server/cloudflare` for edge deployments.
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-kdna-web-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a KDNA-integrated web application with Next.js App Router, Next.js Pages Router, or Express templates.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-kdna-web-app": "bin/create-kdna-web-app.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"docs/",
|
|
12
|
+
"src/",
|
|
13
|
+
"templates/",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"NOTICE",
|
|
16
|
+
"README.md",
|
|
17
|
+
"SECURITY.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"ci": "npm test && npm pack --dry-run --json",
|
|
21
|
+
"test": "node --test tests/**/*.test.js"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {},
|
|
27
|
+
"license": "Apache-2.0",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/aikdna/create-kdna-web-app.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/aikdna/create-kdna-web-app/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/aikdna/create-kdna-web-app",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"kdna",
|
|
38
|
+
"create-kdna-web-app",
|
|
39
|
+
"scaffold",
|
|
40
|
+
"nextjs",
|
|
41
|
+
"express",
|
|
42
|
+
"judgment-asset",
|
|
43
|
+
"starter"
|
|
44
|
+
]
|
|
45
|
+
}
|
package/src/scaffold.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { spawnSync } = require('node:child_process');
|
|
6
|
+
|
|
7
|
+
const VALID_TEMPLATES = new Set(['nextjs', 'nextjs-pages', 'express']);
|
|
8
|
+
const VALID_PACKAGE_MANAGERS = new Set(['npm', 'pnpm', 'yarn']);
|
|
9
|
+
|
|
10
|
+
function usage() {
|
|
11
|
+
return [
|
|
12
|
+
'Usage: create-kdna-web-app <project-name> [options]',
|
|
13
|
+
'',
|
|
14
|
+
'Options:',
|
|
15
|
+
' --template <nextjs|nextjs-pages|express>',
|
|
16
|
+
' --package-manager <npm|pnpm|yarn>',
|
|
17
|
+
' --no-install',
|
|
18
|
+
' --help',
|
|
19
|
+
].join('\n');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseArgs(args) {
|
|
23
|
+
const options = {
|
|
24
|
+
projectName: null,
|
|
25
|
+
template: 'nextjs',
|
|
26
|
+
packageManager: null,
|
|
27
|
+
install: true,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
31
|
+
const arg = args[index];
|
|
32
|
+
if (arg === '--help' || arg === '-h') {
|
|
33
|
+
options.help = true;
|
|
34
|
+
} else if (arg === '--template') {
|
|
35
|
+
options.template = args[++index];
|
|
36
|
+
} else if (arg.startsWith('--template=')) {
|
|
37
|
+
options.template = arg.slice('--template='.length);
|
|
38
|
+
} else if (arg === '--package-manager') {
|
|
39
|
+
options.packageManager = args[++index];
|
|
40
|
+
} else if (arg.startsWith('--package-manager=')) {
|
|
41
|
+
options.packageManager = arg.slice('--package-manager='.length);
|
|
42
|
+
} else if (arg === '--no-install') {
|
|
43
|
+
options.install = false;
|
|
44
|
+
} else if (!options.projectName) {
|
|
45
|
+
options.projectName = arg;
|
|
46
|
+
} else {
|
|
47
|
+
const error = new Error(`Unknown argument: ${arg}\n\n${usage()}`);
|
|
48
|
+
error.exitCode = 1;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return options;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function validateOptions(options) {
|
|
57
|
+
if (options.help) return;
|
|
58
|
+
if (!options.projectName) {
|
|
59
|
+
const error = new Error(`Project name is required.\n\n${usage()}`);
|
|
60
|
+
error.exitCode = 1;
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
if (!VALID_TEMPLATES.has(options.template)) {
|
|
64
|
+
const error = new Error(`Unknown template: ${options.template}`);
|
|
65
|
+
error.exitCode = 1;
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
if (options.packageManager && !VALID_PACKAGE_MANAGERS.has(options.packageManager)) {
|
|
69
|
+
const error = new Error(`Unknown package manager: ${options.packageManager}`);
|
|
70
|
+
error.exitCode = 1;
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function detectPackageManager() {
|
|
76
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
77
|
+
if (userAgent.startsWith('pnpm')) return 'pnpm';
|
|
78
|
+
if (userAgent.startsWith('yarn')) return 'yarn';
|
|
79
|
+
return 'npm';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function packageInstallCommand(packageManager) {
|
|
83
|
+
if (packageManager === 'pnpm') return ['pnpm', ['install']];
|
|
84
|
+
if (packageManager === 'yarn') return ['yarn', []];
|
|
85
|
+
return ['npm', ['install']];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function templateRoot(template) {
|
|
89
|
+
return path.join(__dirname, '..', 'templates', template);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function copyTemplate(srcDir, destDir, variables) {
|
|
93
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
94
|
+
const src = path.join(srcDir, entry.name);
|
|
95
|
+
const name = entry.name === '_gitignore' ? '.gitignore' : entry.name;
|
|
96
|
+
const dest = path.join(destDir, name);
|
|
97
|
+
|
|
98
|
+
if (entry.isDirectory()) {
|
|
99
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
100
|
+
copyTemplate(src, dest, variables);
|
|
101
|
+
} else {
|
|
102
|
+
let content = fs.readFileSync(src, 'utf8');
|
|
103
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
104
|
+
content = content.replaceAll(`{{${key}}}`, value);
|
|
105
|
+
}
|
|
106
|
+
fs.writeFileSync(dest, content);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function assertSafeProjectDir(projectDir) {
|
|
112
|
+
if (fs.existsSync(projectDir) && fs.readdirSync(projectDir).length > 0) {
|
|
113
|
+
const error = new Error(`Target directory is not empty: ${projectDir}`);
|
|
114
|
+
error.exitCode = 1;
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function scaffold(options) {
|
|
120
|
+
validateOptions(options);
|
|
121
|
+
if (options.help) {
|
|
122
|
+
return { help: usage() };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const packageManager = options.packageManager || detectPackageManager();
|
|
126
|
+
const projectDir = path.resolve(options.projectName);
|
|
127
|
+
const projectName = path.basename(projectDir);
|
|
128
|
+
assertSafeProjectDir(projectDir);
|
|
129
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
130
|
+
|
|
131
|
+
copyTemplate(templateRoot(options.template), projectDir, {
|
|
132
|
+
projectName,
|
|
133
|
+
packageManager,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (options.install) {
|
|
137
|
+
const [command, args] = packageInstallCommand(packageManager);
|
|
138
|
+
const result = spawnSync(command, args, { cwd: projectDir, stdio: 'inherit' });
|
|
139
|
+
if (result.status !== 0) {
|
|
140
|
+
const error = new Error(`${command} ${args.join(' ')} failed.`);
|
|
141
|
+
error.exitCode = result.status || 1;
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { projectDir, projectName, template: options.template, packageManager, installed: options.install };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function main(args) {
|
|
150
|
+
const options = parseArgs(args);
|
|
151
|
+
const result = scaffold(options);
|
|
152
|
+
if (result.help) {
|
|
153
|
+
console.log(result.help);
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
console.log(`Created ${result.projectName} with the ${result.template} template.`);
|
|
157
|
+
if (!result.installed) {
|
|
158
|
+
console.log(`Skipped install. Run: cd ${path.relative(process.cwd(), result.projectDir)} && ${result.packageManager} install`);
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
VALID_TEMPLATES,
|
|
165
|
+
parseArgs,
|
|
166
|
+
scaffold,
|
|
167
|
+
main,
|
|
168
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "node src/server.js",
|
|
7
|
+
"start": "node src/server.js",
|
|
8
|
+
"test": "node scripts/smoke.mjs"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@aikdna/kdna-core": "^0.15.10",
|
|
12
|
+
"@aikdna/kdna-web-server": "^0.1.0",
|
|
13
|
+
"express": "^5.2.1"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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">
|
|
6
|
+
<title>KDNA Web App</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<main>
|
|
10
|
+
<input id="file" type="file" accept=".kdna">
|
|
11
|
+
<pre id="output">Choose a .kdna file</pre>
|
|
12
|
+
</main>
|
|
13
|
+
<script>
|
|
14
|
+
const file = document.querySelector('#file')
|
|
15
|
+
const output = document.querySelector('#output')
|
|
16
|
+
file.addEventListener('change', async () => {
|
|
17
|
+
const form = new FormData()
|
|
18
|
+
form.set('file', file.files[0])
|
|
19
|
+
const inspect = await fetch('/api/kdna/inspect', { method: 'POST', body: form }).then((r) => r.json())
|
|
20
|
+
const loaded = await fetch('/api/kdna/load', {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: { 'content-type': 'application/json' },
|
|
23
|
+
body: JSON.stringify({ fileId: inspect.fileId, profile: 'compact' }),
|
|
24
|
+
}).then((r) => r.json())
|
|
25
|
+
output.textContent = loaded.content || JSON.stringify(loaded, null, 2)
|
|
26
|
+
})
|
|
27
|
+
</script>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import { createKDNARouter } from '@aikdna/kdna-web-server/express'
|
|
3
|
+
|
|
4
|
+
const app = express()
|
|
5
|
+
const port = Number(process.env.PORT || 3000)
|
|
6
|
+
|
|
7
|
+
app.use('/api/kdna', createKDNARouter({
|
|
8
|
+
storageDir: process.env.KDNA_STORAGE_DIR ?? '/tmp/kdna-web',
|
|
9
|
+
activationServerUrl: process.env.KDNA_ACTIVATION_URL,
|
|
10
|
+
}))
|
|
11
|
+
app.use(express.static('public'))
|
|
12
|
+
|
|
13
|
+
app.listen(port, () => {
|
|
14
|
+
console.log(`KDNA web app running on http://localhost:${port}`)
|
|
15
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
KDNAAssetInspector,
|
|
5
|
+
KDNAFileDropzone,
|
|
6
|
+
KDNALoadPlanGate,
|
|
7
|
+
KDNAPasswordUnlockDialog,
|
|
8
|
+
} from '@aikdna/kdna-react'
|
|
9
|
+
import { useState } from 'react'
|
|
10
|
+
|
|
11
|
+
export default function Page() {
|
|
12
|
+
const [unlock, setUnlock] = useState(null)
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<main>
|
|
16
|
+
<KDNAFileDropzone endpoint="/api/kdna" onError={console.error}>
|
|
17
|
+
{({ fileId, inspect, loading }) => (
|
|
18
|
+
<section>
|
|
19
|
+
<p>{loading ? 'Uploading...' : 'Drop or choose a .kdna file'}</p>
|
|
20
|
+
{inspect ? <KDNAAssetInspector inspect={inspect} /> : null}
|
|
21
|
+
{fileId ? (
|
|
22
|
+
<KDNALoadPlanGate fileId={fileId} endpoint="/api/kdna">
|
|
23
|
+
{({ status, content }) => {
|
|
24
|
+
if (status === 'locked') {
|
|
25
|
+
return (
|
|
26
|
+
<KDNAPasswordUnlockDialog
|
|
27
|
+
fileId={fileId}
|
|
28
|
+
endpoint="/api/kdna"
|
|
29
|
+
onUnlock={(result) => setUnlock(result.content)}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
return <pre>{unlock || content || status}</pre>
|
|
34
|
+
}}
|
|
35
|
+
</KDNALoadPlanGate>
|
|
36
|
+
) : null}
|
|
37
|
+
</section>
|
|
38
|
+
)}
|
|
39
|
+
</KDNAFileDropzone>
|
|
40
|
+
</main>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"test": "node scripts/smoke.mjs"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@aikdna/kdna-core": "^0.15.10",
|
|
13
|
+
"@aikdna/kdna-react": "^0.1.0",
|
|
14
|
+
"@aikdna/kdna-web-server": "^0.1.0",
|
|
15
|
+
"next": "^16.2.10",
|
|
16
|
+
"react": "^19.2.7",
|
|
17
|
+
"react-dom": "^19.2.7"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createNextHandlers } from '@aikdna/kdna-web-server/nextjs'
|
|
2
|
+
import {
|
|
3
|
+
KDNAAssetInspector,
|
|
4
|
+
KDNAFileDropzone,
|
|
5
|
+
KDNALoadPlanGate,
|
|
6
|
+
KDNAPasswordUnlockDialog,
|
|
7
|
+
} from '@aikdna/kdna-react'
|
|
8
|
+
|
|
9
|
+
for (const [name, value] of Object.entries({
|
|
10
|
+
createNextHandlers,
|
|
11
|
+
KDNAAssetInspector,
|
|
12
|
+
KDNAFileDropzone,
|
|
13
|
+
KDNALoadPlanGate,
|
|
14
|
+
KDNAPasswordUnlockDialog,
|
|
15
|
+
})) {
|
|
16
|
+
if (typeof value !== 'function') {
|
|
17
|
+
throw new Error(`KDNA Next.js template dependency missing export: ${name}`)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log('KDNA Next.js template smoke passed')
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"test": "node scripts/smoke.mjs"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@aikdna/kdna-core": "^0.15.10",
|
|
13
|
+
"@aikdna/kdna-react": "^0.1.0",
|
|
14
|
+
"@aikdna/kdna-web-server": "^0.1.0",
|
|
15
|
+
"next": "^16.2.10",
|
|
16
|
+
"react": "^19.2.7",
|
|
17
|
+
"react-dom": "^19.2.7"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createKDNARouter } from '@aikdna/kdna-web-server/express'
|
|
2
|
+
|
|
3
|
+
export const config = {
|
|
4
|
+
api: {
|
|
5
|
+
bodyParser: false,
|
|
6
|
+
},
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const handler = createKDNARouter({
|
|
10
|
+
basePath: '/api/kdna',
|
|
11
|
+
storageDir: process.env.KDNA_STORAGE_DIR ?? '/tmp/kdna-web',
|
|
12
|
+
activationServerUrl: process.env.KDNA_ACTIVATION_URL,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export default function kdna(req, res) {
|
|
16
|
+
return handler(req, res)
|
|
17
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
KDNAAssetInspector,
|
|
3
|
+
KDNAFileDropzone,
|
|
4
|
+
KDNALoadPlanGate,
|
|
5
|
+
KDNAPasswordUnlockDialog,
|
|
6
|
+
} from '@aikdna/kdna-react'
|
|
7
|
+
import { useState } from 'react'
|
|
8
|
+
|
|
9
|
+
export default function Page() {
|
|
10
|
+
const [unlock, setUnlock] = useState(null)
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<main>
|
|
14
|
+
<KDNAFileDropzone endpoint="/api/kdna" onError={console.error}>
|
|
15
|
+
{({ fileId, inspect, loading }) => (
|
|
16
|
+
<section>
|
|
17
|
+
<p>{loading ? 'Uploading...' : 'Drop or choose a .kdna file'}</p>
|
|
18
|
+
{inspect ? <KDNAAssetInspector inspect={inspect} /> : null}
|
|
19
|
+
{fileId ? (
|
|
20
|
+
<KDNALoadPlanGate fileId={fileId} endpoint="/api/kdna">
|
|
21
|
+
{({ status, content }) => {
|
|
22
|
+
if (status === 'locked') {
|
|
23
|
+
return (
|
|
24
|
+
<KDNAPasswordUnlockDialog
|
|
25
|
+
fileId={fileId}
|
|
26
|
+
endpoint="/api/kdna"
|
|
27
|
+
onUnlock={(result) => setUnlock(result.content)}
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
return <pre>{unlock || content || status}</pre>
|
|
32
|
+
}}
|
|
33
|
+
</KDNALoadPlanGate>
|
|
34
|
+
) : null}
|
|
35
|
+
</section>
|
|
36
|
+
)}
|
|
37
|
+
</KDNAFileDropzone>
|
|
38
|
+
</main>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createKDNARouter } from '@aikdna/kdna-web-server/express'
|
|
2
|
+
import {
|
|
3
|
+
KDNAAssetInspector,
|
|
4
|
+
KDNAFileDropzone,
|
|
5
|
+
KDNALoadPlanGate,
|
|
6
|
+
KDNAPasswordUnlockDialog,
|
|
7
|
+
} from '@aikdna/kdna-react'
|
|
8
|
+
|
|
9
|
+
for (const [name, value] of Object.entries({
|
|
10
|
+
createKDNARouter,
|
|
11
|
+
KDNAAssetInspector,
|
|
12
|
+
KDNAFileDropzone,
|
|
13
|
+
KDNALoadPlanGate,
|
|
14
|
+
KDNAPasswordUnlockDialog,
|
|
15
|
+
})) {
|
|
16
|
+
if (typeof value !== 'function') {
|
|
17
|
+
throw new Error(`KDNA Next.js Pages template dependency missing export: ${name}`)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log('KDNA Next.js Pages template smoke passed')
|