@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.
Files changed (2) hide show
  1. package/index.js +69 -3
  2. 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
- '.env.example': `# PORT=4174
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:4174/admin, create the first account, then build your
132
- site. Your published site is served at http://localhost:4174/.
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useguano/create",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "license": "AGPL-3.0-only",
5
5
  "publishConfig": {
6
6
  "access": "public"