@firenet-designs/fnd-cli 2.0.0 → 2.2.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 +146 -23
- package/dist/commands/backfill-project.d.ts +10 -0
- package/dist/commands/backfill-project.js +61 -0
- package/dist/commands/create-project.d.ts +10 -0
- package/dist/commands/create-project.js +122 -0
- package/dist/commands/token.d.ts +2 -0
- package/dist/commands/token.js +59 -9
- package/dist/commands/workspace/cleanup.d.ts +11 -0
- package/dist/commands/workspace/cleanup.js +46 -0
- package/dist/commands/workspace/index.d.ts +17 -0
- package/dist/commands/workspace/index.js +107 -0
- package/dist/lib/scaffold.d.ts +35 -0
- package/dist/lib/scaffold.js +101 -0
- package/dist/lib/workspace.d.ts +89 -0
- package/dist/lib/workspace.js +214 -0
- package/oclif.manifest.json +171 -1
- package/package.json +14 -4
- package/prompts/init-project-auto.md +183 -0
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"commands": {
|
|
3
|
+
"backfill-project": {
|
|
4
|
+
"aliases": [],
|
|
5
|
+
"args": {
|
|
6
|
+
"shop": {
|
|
7
|
+
"description": "Shopify store handle, passed to Claude as a hint",
|
|
8
|
+
"name": "shop",
|
|
9
|
+
"required": false
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"description": "Backfill missing scaffold files in an EXISTING repo + theme: .gitignore, .shopifyignore, CLAUDE.md. Each is created only if absent — existing files are left untouched. Does NOT git init, pull the theme, commit, or touch GitHub.\n\nRequires the claude CLI (npm install -g @anthropic-ai/claude-code) for the CLAUDE.md step.",
|
|
13
|
+
"examples": [
|
|
14
|
+
"<%= config.bin %> <%= command.id %>",
|
|
15
|
+
"<%= config.bin %> <%= command.id %> my-store",
|
|
16
|
+
"<%= config.bin %> <%= command.id %> my-store \"extra hints\""
|
|
17
|
+
],
|
|
18
|
+
"flags": {},
|
|
19
|
+
"hasDynamicHelp": false,
|
|
20
|
+
"hiddenAliases": [],
|
|
21
|
+
"id": "backfill-project",
|
|
22
|
+
"pluginAlias": "@firenet-designs/fnd-cli",
|
|
23
|
+
"pluginName": "@firenet-designs/fnd-cli",
|
|
24
|
+
"pluginType": "core",
|
|
25
|
+
"strict": false,
|
|
26
|
+
"enableJsonFlag": false,
|
|
27
|
+
"isESM": true,
|
|
28
|
+
"relativePath": [
|
|
29
|
+
"dist",
|
|
30
|
+
"commands",
|
|
31
|
+
"backfill-project.js"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
3
34
|
"create-app": {
|
|
4
35
|
"aliases": [],
|
|
5
36
|
"args": {},
|
|
@@ -40,6 +71,37 @@
|
|
|
40
71
|
"create-app.js"
|
|
41
72
|
]
|
|
42
73
|
},
|
|
74
|
+
"create-project": {
|
|
75
|
+
"aliases": [],
|
|
76
|
+
"args": {
|
|
77
|
+
"shop": {
|
|
78
|
+
"description": "Shopify store handle — pulls the live theme from <shop>.myshopify.com (omit to skip)",
|
|
79
|
+
"name": "shop",
|
|
80
|
+
"required": false
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"description": "Scaffold a new client project: git on branch production, ignore files, Shopify theme pull, a Claude-generated CLAUDE.md, then a private GitHub repo under the FireNet-Designs org.\n\nRequires the claude CLI (npm install -g @anthropic-ai/claude-code) for the CLAUDE.md step.\n\nGitHub auth comes from YOUR environment — run `gh auth login` once, or export GH_TOKEN in your shell profile. This CLI never stores credentials. Override the org with FND_GH_ORG.",
|
|
84
|
+
"examples": [
|
|
85
|
+
"<%= config.bin %> <%= command.id %>",
|
|
86
|
+
"<%= config.bin %> <%= command.id %> my-store",
|
|
87
|
+
"<%= config.bin %> <%= command.id %> my-store \"Acme storefront, Klaviyo\""
|
|
88
|
+
],
|
|
89
|
+
"flags": {},
|
|
90
|
+
"hasDynamicHelp": false,
|
|
91
|
+
"hiddenAliases": [],
|
|
92
|
+
"id": "create-project",
|
|
93
|
+
"pluginAlias": "@firenet-designs/fnd-cli",
|
|
94
|
+
"pluginName": "@firenet-designs/fnd-cli",
|
|
95
|
+
"pluginType": "core",
|
|
96
|
+
"strict": false,
|
|
97
|
+
"enableJsonFlag": false,
|
|
98
|
+
"isESM": true,
|
|
99
|
+
"relativePath": [
|
|
100
|
+
"dist",
|
|
101
|
+
"commands",
|
|
102
|
+
"create-project.js"
|
|
103
|
+
]
|
|
104
|
+
},
|
|
43
105
|
"token": {
|
|
44
106
|
"aliases": [],
|
|
45
107
|
"args": {},
|
|
@@ -120,7 +182,115 @@
|
|
|
120
182
|
"commands",
|
|
121
183
|
"token.js"
|
|
122
184
|
]
|
|
185
|
+
},
|
|
186
|
+
"workspace:cleanup": {
|
|
187
|
+
"aliases": [],
|
|
188
|
+
"args": {},
|
|
189
|
+
"description": "Unmount a leftover reverse-SSHFS mount on the remote — use this if a workspace session dropped before it could clean up after itself.\n\nUnmounts the remote mount and removes the matching entry from your local authorized_keys. With no --mount, it targets the same path `workspace` would use for the current directory.",
|
|
190
|
+
"examples": [
|
|
191
|
+
"<%= config.bin %> <%= command.id %> --ssh user@host",
|
|
192
|
+
"<%= config.bin %> <%= command.id %> --ssh user@host --mount /home/fnd/cole/fnd-cli"
|
|
193
|
+
],
|
|
194
|
+
"flags": {
|
|
195
|
+
"mount": {
|
|
196
|
+
"description": "exact remote mount path to unmount (defaults to the current dir mapping)",
|
|
197
|
+
"name": "mount",
|
|
198
|
+
"hasDynamicHelp": false,
|
|
199
|
+
"multiple": false,
|
|
200
|
+
"type": "option"
|
|
201
|
+
},
|
|
202
|
+
"mount-base": {
|
|
203
|
+
"description": "base dir on the remote, used to derive the default mount path",
|
|
204
|
+
"name": "mount-base",
|
|
205
|
+
"default": "/home/fnd",
|
|
206
|
+
"hasDynamicHelp": false,
|
|
207
|
+
"multiple": false,
|
|
208
|
+
"type": "option"
|
|
209
|
+
},
|
|
210
|
+
"ssh": {
|
|
211
|
+
"description": "remote to connect to, as user@host",
|
|
212
|
+
"name": "ssh",
|
|
213
|
+
"required": true,
|
|
214
|
+
"hasDynamicHelp": false,
|
|
215
|
+
"multiple": false,
|
|
216
|
+
"type": "option"
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"hasDynamicHelp": false,
|
|
220
|
+
"hiddenAliases": [],
|
|
221
|
+
"id": "workspace:cleanup",
|
|
222
|
+
"pluginAlias": "@firenet-designs/fnd-cli",
|
|
223
|
+
"pluginName": "@firenet-designs/fnd-cli",
|
|
224
|
+
"pluginType": "core",
|
|
225
|
+
"strict": true,
|
|
226
|
+
"enableJsonFlag": false,
|
|
227
|
+
"isESM": true,
|
|
228
|
+
"relativePath": [
|
|
229
|
+
"dist",
|
|
230
|
+
"commands",
|
|
231
|
+
"workspace",
|
|
232
|
+
"cleanup.js"
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
"workspace": {
|
|
236
|
+
"aliases": [],
|
|
237
|
+
"args": {},
|
|
238
|
+
"description": "Open a remote workspace: mirror the current directory onto a remote Linux box via reverse SSHFS and drop into a shell there, cleaning everything up on exit.\n\nThe remote reaches your machine through the ssh tunnel (-R), so your local SSH server is never exposed to the internet or port-forwarded. For the back-connection the remote authenticates with its OWN SSH key, which is temporarily added to your local authorized_keys and removed again on exit. Locally you need an SSH *server* running (sshd), not sshfs — sshfs runs on the remote.",
|
|
239
|
+
"examples": [
|
|
240
|
+
"<%= config.bin %> <%= command.id %> --ssh user@203.0.113.4",
|
|
241
|
+
"<%= config.bin %> <%= command.id %> --ssh user@host --port 40222",
|
|
242
|
+
"<%= config.bin %> <%= command.id %> --ssh user@host --mount-base /home/fnd --local-ssh-port 2222"
|
|
243
|
+
],
|
|
244
|
+
"flags": {
|
|
245
|
+
"local-ssh-port": {
|
|
246
|
+
"description": "port your LOCAL SSH server listens on (the tunnel forwards back to this)",
|
|
247
|
+
"name": "local-ssh-port",
|
|
248
|
+
"default": 22,
|
|
249
|
+
"hasDynamicHelp": false,
|
|
250
|
+
"multiple": false,
|
|
251
|
+
"type": "option"
|
|
252
|
+
},
|
|
253
|
+
"mount-base": {
|
|
254
|
+
"description": "base dir on the remote; the mount lands at <base>/<local-user>/<dir-name>",
|
|
255
|
+
"name": "mount-base",
|
|
256
|
+
"default": "/home/fnd",
|
|
257
|
+
"hasDynamicHelp": false,
|
|
258
|
+
"multiple": false,
|
|
259
|
+
"type": "option"
|
|
260
|
+
},
|
|
261
|
+
"port": {
|
|
262
|
+
"char": "p",
|
|
263
|
+
"description": "reverse-tunnel port opened on the remote (random 20000-60000 if omitted)",
|
|
264
|
+
"name": "port",
|
|
265
|
+
"hasDynamicHelp": false,
|
|
266
|
+
"multiple": false,
|
|
267
|
+
"type": "option"
|
|
268
|
+
},
|
|
269
|
+
"ssh": {
|
|
270
|
+
"description": "remote to connect to, as user@host",
|
|
271
|
+
"name": "ssh",
|
|
272
|
+
"required": true,
|
|
273
|
+
"hasDynamicHelp": false,
|
|
274
|
+
"multiple": false,
|
|
275
|
+
"type": "option"
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
"hasDynamicHelp": false,
|
|
279
|
+
"hiddenAliases": [],
|
|
280
|
+
"id": "workspace",
|
|
281
|
+
"pluginAlias": "@firenet-designs/fnd-cli",
|
|
282
|
+
"pluginName": "@firenet-designs/fnd-cli",
|
|
283
|
+
"pluginType": "core",
|
|
284
|
+
"strict": true,
|
|
285
|
+
"enableJsonFlag": false,
|
|
286
|
+
"isESM": true,
|
|
287
|
+
"relativePath": [
|
|
288
|
+
"dist",
|
|
289
|
+
"commands",
|
|
290
|
+
"workspace",
|
|
291
|
+
"index.js"
|
|
292
|
+
]
|
|
123
293
|
}
|
|
124
294
|
},
|
|
125
|
-
"version": "2.
|
|
295
|
+
"version": "2.2.0"
|
|
126
296
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firenet-designs/fnd-cli",
|
|
3
3
|
"description": "A new CLI generated with oclif",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"author": "Cole Denslow",
|
|
6
|
+
"contributors": [
|
|
7
|
+
"Justin Schellenberg"
|
|
8
|
+
],
|
|
6
9
|
"bin": {
|
|
7
10
|
"fnd": "bin/run.js"
|
|
8
11
|
},
|
|
@@ -26,18 +29,22 @@
|
|
|
26
29
|
"eslint": "^9",
|
|
27
30
|
"eslint-config-oclif": "^6",
|
|
28
31
|
"eslint-config-prettier": "^10",
|
|
29
|
-
"mocha": "^
|
|
32
|
+
"mocha": "^11",
|
|
30
33
|
"oclif": "^4",
|
|
31
|
-
"shx": "^0.
|
|
34
|
+
"shx": "^0.4.0",
|
|
32
35
|
"ts-node": "^10",
|
|
33
36
|
"typescript": "^5"
|
|
34
37
|
},
|
|
35
38
|
"engines": {
|
|
36
39
|
"node": ">=24.0.0"
|
|
37
40
|
},
|
|
41
|
+
"overrides": {
|
|
42
|
+
"glob": "^13"
|
|
43
|
+
},
|
|
38
44
|
"files": [
|
|
39
45
|
"./bin",
|
|
40
46
|
"./dist",
|
|
47
|
+
"./prompts",
|
|
41
48
|
"./oclif.manifest.json"
|
|
42
49
|
],
|
|
43
50
|
"homepage": "https://github.com/FireNet-Designs/fnd-cli",
|
|
@@ -52,7 +59,7 @@
|
|
|
52
59
|
},
|
|
53
60
|
"type": "module",
|
|
54
61
|
"oclif": {
|
|
55
|
-
"bin": "fnd
|
|
62
|
+
"bin": "fnd",
|
|
56
63
|
"dirname": "fnd-cli",
|
|
57
64
|
"commands": "./dist/commands",
|
|
58
65
|
"hooks": {
|
|
@@ -67,6 +74,9 @@
|
|
|
67
74
|
"topics": {
|
|
68
75
|
"hello": {
|
|
69
76
|
"description": "Say hello to the world and others"
|
|
77
|
+
},
|
|
78
|
+
"workspace": {
|
|
79
|
+
"description": "Open a remote reverse-SSHFS workspace and run Claude on it"
|
|
70
80
|
}
|
|
71
81
|
}
|
|
72
82
|
},
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Init Project (Auto) — Generate & Write CLAUDE.md, No Questions
|
|
2
|
+
|
|
3
|
+
You are generating a `CLAUDE.md` for the code repo in the **current working directory** and writing it to disk at `./CLAUDE.md`. This is a non-interactive, one-shot run.
|
|
4
|
+
|
|
5
|
+
## Hard rules
|
|
6
|
+
|
|
7
|
+
- **Do NOT ask the user anything.** There is no human watching this run. Infer every field from the repo. Where you genuinely cannot infer a value, use the sensible default noted below — never a placeholder like `[fill in]`.
|
|
8
|
+
- **Write the file yourself** to `./CLAUDE.md` using the Write tool. Do not print it in a code block and stop.
|
|
9
|
+
- If `./CLAUDE.md` already exists, read it first and treat the run as an update: keep any human-written project notes, refresh the inferred sections.
|
|
10
|
+
- Keep the whole thing tight. This file is loaded on every future session in the repo — no filler.
|
|
11
|
+
|
|
12
|
+
## Step 1 — Inspect the repo (read-only)
|
|
13
|
+
|
|
14
|
+
Look at the current directory to infer the fields. Useful signals:
|
|
15
|
+
|
|
16
|
+
- `package.json` → name, scripts (dev/test/lint/typecheck/build/deploy), dependencies (stack + runtime via `engines`), entry (`main`/`module`/`exports`).
|
|
17
|
+
- `shopify.app.toml` → **Shopify App**. Look for an `extensions/` dir → note each extension and its `shopify.extension.toml`.
|
|
18
|
+
- `shopify.theme.toml`, or top-level `templates/ sections/ snippets/ layout/ config/` → **Shopify Theme**.
|
|
19
|
+
- `@shopify/hydrogen` in deps, or `app/routes/` + `remix`/`vite` → **Shopify Hydrogen**.
|
|
20
|
+
- `next.config.*` or `next` in deps → **Next.js**.
|
|
21
|
+
- An Express/Fastify/Hono server entry with no frontend → **Node API**.
|
|
22
|
+
- Anything else → **Custom** (write 4-6 conventions tailored to the actual stack).
|
|
23
|
+
- `tsconfig.json`, lockfile type (`package-lock.json`/`pnpm-lock.yaml`/`yarn.lock`/`bun.lockb`), `.nvmrc`/`engines` → runtime + package manager.
|
|
24
|
+
- `.env.example` / `.env.sample` → external services (Klaviyo, Stripe, Shopify Admin API, a DB URL, etc.).
|
|
25
|
+
- Top two levels of the directory tree → Project Structure block.
|
|
26
|
+
|
|
27
|
+
Defaults when a signal is missing: Type = `Custom`; Runtime = latest LTS Node; Build output = `none`; External Services = omit the section; commands = omit rows that don't exist.
|
|
28
|
+
|
|
29
|
+
## Step 2 — Write `./CLAUDE.md` with exactly this structure
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
# [Project Name]
|
|
33
|
+
|
|
34
|
+
**Type:** [Project Type]
|
|
35
|
+
**Stack:** [Stack — comma separated]
|
|
36
|
+
**Runtime:** [Runtime + package manager]
|
|
37
|
+
|
|
38
|
+
## What This Is
|
|
39
|
+
|
|
40
|
+
[One or two sentences inferred from README/package.json description. If none, describe it from the code.]
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Engineering Principles
|
|
45
|
+
|
|
46
|
+
These apply to every file touched in this project.
|
|
47
|
+
|
|
48
|
+
| Principle | Rule |
|
|
49
|
+
|-----------|------|
|
|
50
|
+
| Separation of concerns | One module, one reason to change |
|
|
51
|
+
| No side effects at the core | Pure logic in the middle, IO/network/DB/filesystem only at the edges |
|
|
52
|
+
| Open/Closed | Extend by adding — modify existing code only to fix bugs, never for a new caller |
|
|
53
|
+
| WET before DRY | Duplicate until three identical-shape instances, then extract |
|
|
54
|
+
| No unnecessary abstraction | If you can delete it and nothing breaks, delete it |
|
|
55
|
+
| Boundary validation only | Validate user input and external API responses — trust internal code |
|
|
56
|
+
| Comments explain WHY | Not what. Never describe what the code does. |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Project Structure
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
[Top two levels of the directory tree]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Entry point:** `[entry point]`
|
|
67
|
+
**Build output:** `[build output]` — generated, never edit directly
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Conventions
|
|
72
|
+
|
|
73
|
+
[Insert the matching preset block below for the detected type. For Custom, write 4-6 stack-specific rules, one per line, imperative voice.]
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Workflow
|
|
78
|
+
|
|
79
|
+
| Task | Command |
|
|
80
|
+
|------|---------|
|
|
81
|
+
| Dev | `[dev]` |
|
|
82
|
+
| Test | `[test]` |
|
|
83
|
+
| Lint | `[lint]` |
|
|
84
|
+
| Type check | `[typecheck]` |
|
|
85
|
+
| Build | `[build]` |
|
|
86
|
+
| Deploy | `[deploy]` |
|
|
87
|
+
|
|
88
|
+
(Omit rows with no real command.)
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## External Services
|
|
93
|
+
|
|
94
|
+
[One line per service + what it's for. Omit this whole section if none.]
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Package Security
|
|
99
|
+
|
|
100
|
+
- Never install a package younger than 14 days — let the community catch malicious releases first.
|
|
101
|
+
- Read every install command before approving the dependencies it pulls in.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Agent Behavior
|
|
106
|
+
|
|
107
|
+
- **Never ask the user to test, run, or verify anything.** Claude owns the full dev loop — write, run, debug, fix, repeat until it works.
|
|
108
|
+
- If something genuinely cannot be verified (e.g. a visual UI state), say so explicitly. Never claim success without verifying.
|
|
109
|
+
- **Destructive commands require confirmation first.** Destructive = force push, `reset --hard`, deleting files/branches, dropping tables, removing packages, overwriting uncommitted work.
|
|
110
|
+
- Everything else: just run it. For complex or non-obvious commands, give one line of context — then run without waiting.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Do Not Touch
|
|
115
|
+
|
|
116
|
+
- `[build output]` — generated output, never edit directly
|
|
117
|
+
- `.env` / `.env.*` — never read or write secret values; use `.env.example` for shape only
|
|
118
|
+
- Lockfiles — source of truth, never edit manually
|
|
119
|
+
- `node_modules/` — never edit
|
|
120
|
+
[Any extra off-limits dirs you spotted]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Convention presets
|
|
124
|
+
|
|
125
|
+
### Shopify Theme
|
|
126
|
+
```
|
|
127
|
+
- Liquid templates live in `templates/`, `sections/`, `snippets/`, `layout/`
|
|
128
|
+
- CSS: `assets/[name].css`; JS: `assets/[name].js` — no bundler unless one is set up
|
|
129
|
+
- `config/settings_data.json` is written by Shopify admin — read but treat as volatile
|
|
130
|
+
- `config/settings_schema.json` is the source of truth for theme settings
|
|
131
|
+
- Never inline styles or scripts in Liquid — use asset references
|
|
132
|
+
- `{% schema %}` blocks must be valid JSON; verify with `shopify theme check`
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Shopify App
|
|
136
|
+
```
|
|
137
|
+
- App entry: `[entry]`; Shopify CLI manages tunneling + dev environment
|
|
138
|
+
- `shopify.app.toml` — app config, commit it
|
|
139
|
+
- Extensions live in `extensions/[name]/`, each with its own `shopify.extension.toml`
|
|
140
|
+
- Never hardcode shop domain or access tokens — read from env or session
|
|
141
|
+
- Webhook handlers must verify HMAC before processing
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**If this is a Shopify App with extensions, ALSO append this block to the `Conventions` section verbatim:**
|
|
145
|
+
```
|
|
146
|
+
### Extension Constraints (hard platform limits)
|
|
147
|
+
- **No third-party libraries inside extension code.** Extensions run in a constrained sandbox — write plain code, no npm deps bundled into the extension.
|
|
148
|
+
- Hard limits per extension: **128 kB input / 11M instructions / 20 kB output.** Exceeding any of these fails the function at runtime.
|
|
149
|
+
- Keep extension logic minimal and allocation-free in hot paths; do work in the app backend where possible.
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Shopify Hydrogen
|
|
153
|
+
```
|
|
154
|
+
- Remix file routing: `app/routes/` maps 1:1 to URL segments
|
|
155
|
+
- Loaders run server-side; actions handle mutations
|
|
156
|
+
- Storefront API queries live in `app/graphql/` as `.graphql` files
|
|
157
|
+
- Set cache headers in the loader return — never skip them on product/collection routes
|
|
158
|
+
- `public/` is served as-is; `app/assets/` goes through Vite
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Next.js
|
|
162
|
+
```
|
|
163
|
+
- App Router: `app/`; `page.tsx` = route, `layout.tsx` = shell
|
|
164
|
+
- Server Components by default — add `"use client"` only when necessary
|
|
165
|
+
- API routes: `app/api/[route]/route.ts`
|
|
166
|
+
- Only `NEXT_PUBLIC_`-prefixed env vars are client-visible
|
|
167
|
+
- Always use `next/image` for content images, never raw `<img>`
|
|
168
|
+
- Never fetch in a Client Component when a Server Component can do it
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Node API
|
|
172
|
+
```
|
|
173
|
+
- Route handlers in `src/routes/`, one file per resource
|
|
174
|
+
- Middleware in `src/middleware/`, applied in `src/app.ts`
|
|
175
|
+
- Validate request bodies at the route boundary only — not inside service functions
|
|
176
|
+
- Service functions are pure: typed inputs, typed outputs
|
|
177
|
+
- Database access only inside `src/db/` or `src/repositories/` — never in routes
|
|
178
|
+
- Environment config loaded once in `src/config.ts`
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Step 3 — Confirm
|
|
182
|
+
|
|
183
|
+
After writing, print one line: the absolute path of the file written and the detected project type. Nothing else.
|