@useguano/create 0.1.4 → 0.1.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/index.js +69 -3
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// The server binds the token to the first admin at /admin setup, and the
|
|
14
14
|
// setup form shows the permissions instead of asking them again.
|
|
15
15
|
import { spawnSync } from 'node:child_process'
|
|
16
|
+
import { createServer } from 'node:net'
|
|
16
17
|
import { existsSync } from 'node:fs'
|
|
17
18
|
import { mkdir, readdir, writeFile } from 'node:fs/promises'
|
|
18
19
|
import { basename, join, resolve } from 'node:path'
|
|
@@ -73,6 +74,20 @@ const name =
|
|
|
73
74
|
.replace(/[^a-z0-9-]+/g, '-')
|
|
74
75
|
.replace(/^-+|-+$/g, '') || DEFAULT_DIR
|
|
75
76
|
|
|
77
|
+
/**
|
|
78
|
+
* The same thing, as a NAME rather than a package slug.
|
|
79
|
+
*
|
|
80
|
+
* `acme-landing` is what npm needs and what the folder is called; "Acme
|
|
81
|
+
* Landing" is what belongs in a field labelled Project name. The setup screen
|
|
82
|
+
* prefills it and the human can change it there — this is a head start, not a
|
|
83
|
+
* decision.
|
|
84
|
+
*/
|
|
85
|
+
const displayName = name
|
|
86
|
+
.split('-')
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.map((w) => w[0].toUpperCase() + w.slice(1))
|
|
89
|
+
.join(' ')
|
|
90
|
+
|
|
76
91
|
await mkdir(target, { recursive: true })
|
|
77
92
|
if ((await readdir(target)).length) {
|
|
78
93
|
rl?.close()
|
|
@@ -80,6 +95,39 @@ if ((await readdir(target)).length) {
|
|
|
80
95
|
process.exit(1)
|
|
81
96
|
}
|
|
82
97
|
|
|
98
|
+
/**
|
|
99
|
+
* A free port for THIS project, written into .env as an explicit PORT.
|
|
100
|
+
*
|
|
101
|
+
* Every project used to default to 4174, so a second one on the same machine
|
|
102
|
+
* lost the race and walked to the next free port — which left its MCP entry,
|
|
103
|
+
* the Vite proxy and any open tab addressing the FIRST project's socket, where
|
|
104
|
+
* its own token is unknown and the 401 reads as "the token is dead". An
|
|
105
|
+
* explicit port is a contract the server honours instead of walking, and
|
|
106
|
+
* putting it in a file the user can read makes "which port is this site on" a
|
|
107
|
+
* question with an answer.
|
|
108
|
+
*
|
|
109
|
+
* 4174 is still preferred, so a first project looks exactly as documented.
|
|
110
|
+
*/
|
|
111
|
+
async function pickPort(first = 4174, tries = 40) {
|
|
112
|
+
// One port per site: the preview is served by the same server under
|
|
113
|
+
// /_guano/preview, so there is no second port to keep free above it.
|
|
114
|
+
for (let port = first; port < first + tries; port += 1) {
|
|
115
|
+
if (await free(port)) return port
|
|
116
|
+
}
|
|
117
|
+
return first
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function free(port) {
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
const probe = createServer()
|
|
123
|
+
probe.once('error', () => resolve(false))
|
|
124
|
+
probe.once('listening', () => probe.close(() => resolve(true)))
|
|
125
|
+
probe.listen(port, '0.0.0.0')
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const port = await pickPort()
|
|
130
|
+
|
|
83
131
|
const files = {
|
|
84
132
|
'package.json':
|
|
85
133
|
JSON.stringify(
|
|
@@ -107,7 +155,22 @@ const files = {
|
|
|
107
155
|
export default {}
|
|
108
156
|
`,
|
|
109
157
|
|
|
110
|
-
|
|
158
|
+
// A real .env, not just the example: the port is this project's identity on
|
|
159
|
+
// the machine, and leaving it to the 4174 default is what makes two projects
|
|
160
|
+
// fight over one address.
|
|
161
|
+
'.env': `# This project's own port. Keep it — two Guano sites on one machine
|
|
162
|
+
# must not share one, or whichever starts first answers for both and the
|
|
163
|
+
# other's MCP token is refused with a 401.
|
|
164
|
+
PORT=${port}
|
|
165
|
+
|
|
166
|
+
# What you called this project in the terminal. It prefills the Project name
|
|
167
|
+
# on the setup screen, so nobody is asked to name the same thing twice. Only
|
|
168
|
+
# read before the first admin account exists; edit the name in the editor
|
|
169
|
+
# after that.
|
|
170
|
+
GUANO_PROJECT_NAME=${displayName}
|
|
171
|
+
`,
|
|
172
|
+
|
|
173
|
+
'.env.example': `PORT=${port}
|
|
111
174
|
# GUANO_DATA_DIR=./data # users, media, drafts, the published site — back this up
|
|
112
175
|
# COOKIE_SECURE=1 # set 0 only for plain-HTTP LAN setups
|
|
113
176
|
# PUBLISH_TOKEN= # optional: token for CI publishes
|
|
@@ -128,8 +191,11 @@ A [Guano](https://www.npmjs.com/package/@useguano/guano) site.
|
|
|
128
191
|
npm run dev
|
|
129
192
|
\`\`\`
|
|
130
193
|
|
|
131
|
-
Open http://localhost
|
|
132
|
-
site. Your published site is served at http://localhost
|
|
194
|
+
Open http://localhost:${port}/admin, create the first account, then build your
|
|
195
|
+
site. Your published site is served at http://localhost:${port}/.
|
|
196
|
+
|
|
197
|
+
This project runs on port ${port} (set in \`.env\`). Each Guano site on a
|
|
198
|
+
machine needs its own, so two of them never answer for one address.
|
|
133
199
|
|
|
134
200
|
If you said yes to Claude Desktop when scaffolding, it is already connected:
|
|
135
201
|
the agent token is bound to the admin account you create, and the permissions
|