client-handover 1.0.5 → 1.0.6
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 +5 -0
- package/cli.js +12 -5
- package/credentials.js +3 -2
- package/deploy.js +3 -2
- package/handover.js +3 -2
- package/license.js +3 -2
- package/package.json +2 -1
- package/scanner.js +193 -0
- package/setup.js +3 -2
package/README.md
CHANGED
|
@@ -200,6 +200,11 @@ client-handover/
|
|
|
200
200
|
|
|
201
201
|
## Changelog
|
|
202
202
|
|
|
203
|
+
### 1.0.6
|
|
204
|
+
- Auto-scans your actual project files on every run — reads package.json, config files, env variable keys, deploy configs, folder structure, and more
|
|
205
|
+
- Generated documents are now tailored to your real project, not generic templates
|
|
206
|
+
- Extra notes file still supported as optional additional context
|
|
207
|
+
|
|
203
208
|
### 1.0.5
|
|
204
209
|
- Interactive API key setup prompt on install — no manual config needed
|
|
205
210
|
- Added `handover key <api-key>` command to save key at any time
|
package/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { credentials } from './credentials.js'
|
|
|
6
6
|
import { handover } from './handover.js'
|
|
7
7
|
import { license } from './license.js'
|
|
8
8
|
import { generateDoc, saveApiKey } from './generator.js'
|
|
9
|
+
import { scanProject } from './scanner.js'
|
|
9
10
|
import chalk from 'chalk'
|
|
10
11
|
import fs from 'fs'
|
|
11
12
|
import path from 'path'
|
|
@@ -42,7 +43,7 @@ function printHelp() {
|
|
|
42
43
|
console.log()
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
async function runAll(projectInfo, folderName) {
|
|
46
|
+
async function runAll(projectInfo, folderName, outputName) {
|
|
46
47
|
const sections = [
|
|
47
48
|
{ key: 'handover', fn: handover, label: 'Full Handover Document' },
|
|
48
49
|
{ key: 'setup', fn: setup, label: 'Project Setup & Dependencies' },
|
|
@@ -86,17 +87,23 @@ async function main() {
|
|
|
86
87
|
process.exit(0)
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
//
|
|
90
|
-
|
|
90
|
+
// Scan the current project directory
|
|
91
|
+
console.log(chalk.dim('\n🔍 Scanning project...'))
|
|
92
|
+
const scannedContext = scanProject(process.cwd())
|
|
93
|
+
|
|
94
|
+
// Read optional extra project info file
|
|
95
|
+
let extraInfo = ''
|
|
91
96
|
if (infoFile) {
|
|
92
97
|
if (!fs.existsSync(infoFile)) {
|
|
93
98
|
console.error(chalk.red(`\n❌ File not found: ${infoFile}\n`))
|
|
94
99
|
process.exit(1)
|
|
95
100
|
}
|
|
96
|
-
|
|
97
|
-
console.log(chalk.green(
|
|
101
|
+
extraInfo = fs.readFileSync(infoFile, 'utf-8')
|
|
102
|
+
console.log(chalk.green(`📄 Loaded extra info from: ${infoFile}`))
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
const projectInfo = [scannedContext, extraInfo].filter(Boolean).join('\n\n---\n\n')
|
|
106
|
+
|
|
100
107
|
if (command === 'all') {
|
|
101
108
|
const folderName = outputName || 'all'
|
|
102
109
|
try {
|
package/credentials.js
CHANGED
|
@@ -4,8 +4,9 @@ You are a technical documentation writer creating a CREDENTIALS section for a fr
|
|
|
4
4
|
|
|
5
5
|
⚠️ IMPORTANT: Never include real passwords, keys, or secrets. All sensitive values must use placeholder format: YOUR_VALUE_HERE or [REPLACE_WITH_ACTUAL_VALUE]
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
The following context has been automatically scanned from the developer's actual project files. Use the real environment variable keys and detected services to build the credentials table for THIS specific project. Do not invent services that are not present. Mark values as "[to be filled in]".
|
|
8
|
+
|
|
9
|
+
${projectInfo || '[No project data available — use placeholder examples]'}
|
|
9
10
|
|
|
10
11
|
Generate a CREDENTIALS & ACCESS section that includes:
|
|
11
12
|
|
package/deploy.js
CHANGED
|
@@ -6,8 +6,9 @@ The audience is TWO types of readers:
|
|
|
6
6
|
1. A NON-TECHNICAL CLIENT — plain English, no jargon
|
|
7
7
|
2. A DEVELOPER — exact technical steps
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
The following context has been automatically scanned from the developer's actual project files (deploy configs, package.json scripts, workflow files, etc.). Use it to generate deployment documentation specific to THIS project — do not use generic placeholders. Mark any missing details as "[to be filled in]".
|
|
10
|
+
|
|
11
|
+
${projectInfo || '[No project data available — use placeholder examples]'}
|
|
11
12
|
|
|
12
13
|
Generate a DEPLOYMENT section that includes:
|
|
13
14
|
|
package/handover.js
CHANGED
|
@@ -6,8 +6,9 @@ This document is for TWO audiences:
|
|
|
6
6
|
1. NON-TECHNICAL CLIENT — plain English, reassuring tone, no jargon. They need to feel confident owning this site.
|
|
7
7
|
2. DEVELOPER (future maintainer) — precise, technical, complete. They need to be able to pick this up cold.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
The following context has been automatically scanned from the developer's actual project files (package.json, config files, folder structure, environment variable keys, deploy configs, README, etc.). Use this to generate a document specific to THIS project — do not invent or use generic placeholder examples. If a specific detail is not available in the scanned data, note it as "[to be filled in]" rather than guessing.
|
|
10
|
+
|
|
11
|
+
${projectInfo || '[No project data available — use placeholder examples throughout]'}
|
|
11
12
|
|
|
12
13
|
Generate a full handover document with ALL of the following sections:
|
|
13
14
|
|
package/license.js
CHANGED
|
@@ -2,8 +2,9 @@ export function license(projectInfo = '') {
|
|
|
2
2
|
return `
|
|
3
3
|
You are a technical documentation writer creating a LICENSING & ATTRIBUTION section for a frontend website handover document.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
The following context has been automatically scanned from the developer's actual project files. Use the real dependencies, license file, and detected libraries to generate licensing documentation specific to THIS project — do not use generic placeholder libraries.
|
|
6
|
+
|
|
7
|
+
${projectInfo || '[No project data available — use placeholder examples]'}
|
|
7
8
|
|
|
8
9
|
Generate a LICENSING & ATTRIBUTION section that includes:
|
|
9
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "client-handover",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "AI-powered handover document generator for frontend developers handing off client websites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"credentials.js",
|
|
18
18
|
"license.js",
|
|
19
19
|
"postinstall.js",
|
|
20
|
+
"scanner.js",
|
|
20
21
|
"README.md"
|
|
21
22
|
],
|
|
22
23
|
"scripts": {
|
package/scanner.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
const CONFIG_FILES = [
|
|
5
|
+
'vite.config.js', 'vite.config.ts',
|
|
6
|
+
'next.config.js', 'next.config.ts', 'next.config.mjs',
|
|
7
|
+
'nuxt.config.js', 'nuxt.config.ts',
|
|
8
|
+
'astro.config.js', 'astro.config.ts', 'astro.config.mjs',
|
|
9
|
+
'svelte.config.js',
|
|
10
|
+
'remix.config.js',
|
|
11
|
+
'tailwind.config.js', 'tailwind.config.ts',
|
|
12
|
+
'postcss.config.js',
|
|
13
|
+
'netlify.toml',
|
|
14
|
+
'vercel.json',
|
|
15
|
+
'render.yaml',
|
|
16
|
+
'railway.json',
|
|
17
|
+
'.htaccess',
|
|
18
|
+
'Dockerfile',
|
|
19
|
+
'docker-compose.yml',
|
|
20
|
+
'firebase.json',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const DEPLOY_WORKFLOW_DIR = '.github/workflows'
|
|
24
|
+
|
|
25
|
+
function readFileSafe(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
return fs.readFileSync(filePath, 'utf-8')
|
|
28
|
+
} catch {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getEnvKeys(filePath) {
|
|
34
|
+
const content = readFileSafe(filePath)
|
|
35
|
+
if (!content) return []
|
|
36
|
+
return content
|
|
37
|
+
.split('\n')
|
|
38
|
+
.map(l => l.trim())
|
|
39
|
+
.filter(l => l && !l.startsWith('#') && l.includes('='))
|
|
40
|
+
.map(l => l.split('=')[0].trim())
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getFolderStructure(dir, depth = 0, maxDepth = 2) {
|
|
44
|
+
if (depth > maxDepth) return []
|
|
45
|
+
const ignore = new Set(['node_modules', '.git', '.next', '.nuxt', 'dist', 'build', '.cache', 'coverage', '.turbo'])
|
|
46
|
+
let entries = []
|
|
47
|
+
try {
|
|
48
|
+
const items = fs.readdirSync(dir, { withFileTypes: true })
|
|
49
|
+
for (const item of items) {
|
|
50
|
+
if (ignore.has(item.name) || item.name.startsWith('.')) continue
|
|
51
|
+
const indent = ' '.repeat(depth)
|
|
52
|
+
if (item.isDirectory()) {
|
|
53
|
+
entries.push(`${indent}${item.name}/`)
|
|
54
|
+
entries.push(...getFolderStructure(path.join(dir, item.name), depth + 1, maxDepth))
|
|
55
|
+
} else {
|
|
56
|
+
entries.push(`${indent}${item.name}`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
60
|
+
return entries
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function detectFramework(deps = {}, devDeps = {}) {
|
|
64
|
+
const all = { ...deps, ...devDeps }
|
|
65
|
+
if (all['next']) return 'Next.js'
|
|
66
|
+
if (all['nuxt'] || all['nuxt3']) return 'Nuxt'
|
|
67
|
+
if (all['@astrojs/core'] || all['astro']) return 'Astro'
|
|
68
|
+
if (all['@sveltejs/kit']) return 'SvelteKit'
|
|
69
|
+
if (all['svelte']) return 'Svelte'
|
|
70
|
+
if (all['@remix-run/react']) return 'Remix'
|
|
71
|
+
if (all['gatsby']) return 'Gatsby'
|
|
72
|
+
if (all['react']) return 'React'
|
|
73
|
+
if (all['vue']) return 'Vue'
|
|
74
|
+
if (all['angular']) return 'Angular'
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function scanProject(dir = process.cwd()) {
|
|
79
|
+
const sections = []
|
|
80
|
+
|
|
81
|
+
// --- package.json ---
|
|
82
|
+
const pkgPath = path.join(dir, 'package.json')
|
|
83
|
+
let pkg = null
|
|
84
|
+
if (fs.existsSync(pkgPath)) {
|
|
85
|
+
try {
|
|
86
|
+
pkg = JSON.parse(readFileSafe(pkgPath))
|
|
87
|
+
const framework = detectFramework(pkg.dependencies, pkg.devDependencies)
|
|
88
|
+
sections.push(`## package.json`)
|
|
89
|
+
sections.push(`Name: ${pkg.name || 'unknown'}`)
|
|
90
|
+
if (pkg.description) sections.push(`Description: ${pkg.description}`)
|
|
91
|
+
if (pkg.version) sections.push(`Version: ${pkg.version}`)
|
|
92
|
+
if (framework) sections.push(`Detected framework: ${framework}`)
|
|
93
|
+
if (pkg.engines?.node) sections.push(`Node requirement: ${pkg.engines.node}`)
|
|
94
|
+
|
|
95
|
+
if (pkg.scripts && Object.keys(pkg.scripts).length) {
|
|
96
|
+
sections.push(`\nScripts:`)
|
|
97
|
+
for (const [k, v] of Object.entries(pkg.scripts)) {
|
|
98
|
+
sections.push(` ${k}: ${v}`)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const deps = Object.keys(pkg.dependencies || {})
|
|
103
|
+
const devDeps = Object.keys(pkg.devDependencies || {})
|
|
104
|
+
if (deps.length) sections.push(`\nDependencies: ${deps.join(', ')}`)
|
|
105
|
+
if (devDeps.length) sections.push(`Dev dependencies: ${devDeps.join(', ')}`)
|
|
106
|
+
} catch {}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- Package manager ---
|
|
110
|
+
let pm = 'npm'
|
|
111
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) pm = 'yarn'
|
|
112
|
+
else if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) pm = 'pnpm'
|
|
113
|
+
else if (fs.existsSync(path.join(dir, 'bun.lockb'))) pm = 'bun'
|
|
114
|
+
sections.push(`\nPackage manager: ${pm}`)
|
|
115
|
+
|
|
116
|
+
// --- Environment variables ---
|
|
117
|
+
const envExampleKeys = getEnvKeys(path.join(dir, '.env.example'))
|
|
118
|
+
const envLocalKeys = getEnvKeys(path.join(dir, '.env.local'))
|
|
119
|
+
const envKeys = getEnvKeys(path.join(dir, '.env'))
|
|
120
|
+
const allEnvKeys = [...new Set([...envExampleKeys, ...envLocalKeys, ...envKeys])]
|
|
121
|
+
if (allEnvKeys.length) {
|
|
122
|
+
sections.push(`\n## Environment Variables (keys only)`)
|
|
123
|
+
allEnvKeys.forEach(k => sections.push(` ${k}`))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --- Config files ---
|
|
127
|
+
for (const file of CONFIG_FILES) {
|
|
128
|
+
const filePath = path.join(dir, file)
|
|
129
|
+
if (fs.existsSync(filePath)) {
|
|
130
|
+
const content = readFileSafe(filePath)
|
|
131
|
+
if (content && content.length < 4000) {
|
|
132
|
+
sections.push(`\n## ${file}`)
|
|
133
|
+
sections.push('```')
|
|
134
|
+
sections.push(content.trim())
|
|
135
|
+
sections.push('```')
|
|
136
|
+
} else if (content) {
|
|
137
|
+
sections.push(`\n## ${file} (truncated)`)
|
|
138
|
+
sections.push('```')
|
|
139
|
+
sections.push(content.trim().slice(0, 4000) + '\n...')
|
|
140
|
+
sections.push('```')
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- GitHub Actions workflows ---
|
|
146
|
+
const workflowDir = path.join(dir, DEPLOY_WORKFLOW_DIR)
|
|
147
|
+
if (fs.existsSync(workflowDir)) {
|
|
148
|
+
try {
|
|
149
|
+
const files = fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml'))
|
|
150
|
+
for (const file of files) {
|
|
151
|
+
const content = readFileSafe(path.join(workflowDir, file))
|
|
152
|
+
if (content) {
|
|
153
|
+
sections.push(`\n## .github/workflows/${file}`)
|
|
154
|
+
sections.push('```yaml')
|
|
155
|
+
sections.push(content.trim().slice(0, 3000))
|
|
156
|
+
sections.push('```')
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch {}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- README ---
|
|
163
|
+
const readmePath = path.join(dir, 'README.md')
|
|
164
|
+
if (fs.existsSync(readmePath)) {
|
|
165
|
+
const content = readFileSafe(readmePath)
|
|
166
|
+
if (content) {
|
|
167
|
+
sections.push(`\n## README.md`)
|
|
168
|
+
sections.push(content.trim().slice(0, 3000))
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// --- License ---
|
|
173
|
+
for (const f of ['LICENSE', 'LICENSE.md', 'LICENSE.txt']) {
|
|
174
|
+
const p = path.join(dir, f)
|
|
175
|
+
if (fs.existsSync(p)) {
|
|
176
|
+
const content = readFileSafe(p)
|
|
177
|
+
if (content) {
|
|
178
|
+
sections.push(`\n## ${f}`)
|
|
179
|
+
sections.push(content.trim().slice(0, 500))
|
|
180
|
+
}
|
|
181
|
+
break
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- Folder structure ---
|
|
186
|
+
const structure = getFolderStructure(dir)
|
|
187
|
+
if (structure.length) {
|
|
188
|
+
sections.push(`\n## Project folder structure`)
|
|
189
|
+
sections.push(structure.join('\n'))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return sections.join('\n')
|
|
193
|
+
}
|
package/setup.js
CHANGED
|
@@ -6,8 +6,9 @@ The audience is TWO types of readers:
|
|
|
6
6
|
1. A NON-TECHNICAL CLIENT — who needs plain-English explanations of what everything is and why it matters
|
|
7
7
|
2. A DEVELOPER — who needs exact commands, file paths, and technical detail
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
The following context has been automatically scanned from the developer's actual project files. Use it to generate documentation specific to THIS project — do not use generic placeholders. If something is unclear from the scanned data, mark it as "[to be filled in]".
|
|
10
|
+
|
|
11
|
+
${projectInfo || '[No project data available — use placeholder examples]'}
|
|
11
12
|
|
|
12
13
|
Generate a SETUP section that includes:
|
|
13
14
|
|