create-hollware 0.11.0 → 1.0.0-rc.1
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/dist/index.mjs +91 -19
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { cpSync, existsSync, readFileSync, readdirSync, realpathSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { appendFileSync, cpSync, existsSync, readFileSync, readdirSync, realpathSync, renameSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
-
import { basename, join, resolve } from "node:path";
|
|
5
|
+
import { basename, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
//#region src/scaffold.ts
|
|
8
8
|
const USAGE = [
|
|
@@ -21,6 +21,12 @@ const USAGE = [
|
|
|
21
21
|
" Whether the activity log keeps a keyed hash of each address (default: off)",
|
|
22
22
|
" --audit-retention-days <n>",
|
|
23
23
|
" Days each line of the activity log is kept, 30 to 3650 (default: forever)",
|
|
24
|
+
" --token-file <path>",
|
|
25
|
+
" Write the first token to this file (mode 0600) and print its path, not the token;",
|
|
26
|
+
" a file that exists is refused. Inside the project, it goes into .gitignore",
|
|
27
|
+
" --json One JSON object on stdout and nothing else: dir, then what \"hollware init\" says",
|
|
28
|
+
" (site, ownerId, tokenId, token or tokenFile, expiresAt, setupUrl, mcpUrl,",
|
|
29
|
+
" readApiUrl), the commands to start it and the MCP configuration",
|
|
24
30
|
" --pm <npm|pnpm> The package manager that installs (default: npm)",
|
|
25
31
|
" --yes Take the defaults: <dir> is my-hollware when it is not given",
|
|
26
32
|
" --no-install Do not install, migrate or make the site",
|
|
@@ -42,7 +48,6 @@ const WORKER_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
|
42
48
|
const SITE_KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
43
49
|
/** What `@hollware/*` and `hollware` say in the template's package.json. */
|
|
44
50
|
const VERSION_PLACEHOLDER = "HOLLWARE_VERSION";
|
|
45
|
-
const TOKEN = /hw_pat_[0-9a-hjkmnp-tv-z]{52}/;
|
|
46
51
|
/** The choices of a run, from its arguments. */
|
|
47
52
|
function readChoices(argv) {
|
|
48
53
|
const { values, positionals } = (() => {
|
|
@@ -60,7 +65,9 @@ function readChoices(argv) {
|
|
|
60
65
|
pm: { type: "string" },
|
|
61
66
|
yes: { type: "boolean" },
|
|
62
67
|
"no-install": { type: "boolean" },
|
|
63
|
-
"no-git": { type: "boolean" }
|
|
68
|
+
"no-git": { type: "boolean" },
|
|
69
|
+
"token-file": { type: "string" },
|
|
70
|
+
json: { type: "boolean" }
|
|
64
71
|
}
|
|
65
72
|
});
|
|
66
73
|
} catch (error) {
|
|
@@ -84,6 +91,8 @@ function readChoices(argv) {
|
|
|
84
91
|
pm,
|
|
85
92
|
install: values["no-install"] !== true,
|
|
86
93
|
git: values["no-git"] !== true,
|
|
94
|
+
tokenFile: values["token-file"],
|
|
95
|
+
json: values.json === true,
|
|
87
96
|
initFlags: [
|
|
88
97
|
...values.preset === void 0 ? [] : ["--preset", values.preset],
|
|
89
98
|
...values.locales === void 0 ? [] : ["--locales", values.locales],
|
|
@@ -127,44 +136,107 @@ async function step(io, what, command, args, cwd) {
|
|
|
127
136
|
}
|
|
128
137
|
return result;
|
|
129
138
|
}
|
|
139
|
+
/** The environment variable that holds the token in an MCP client's configuration. */
|
|
140
|
+
const TOKEN_VARIABLE = "HOLLWARE_TOKEN";
|
|
141
|
+
/** The `.mcp.json` block of a Hollware: the URL of /mcp, and a header that names HOLLWARE_TOKEN. */
|
|
142
|
+
function mcpJson(url) {
|
|
143
|
+
return JSON.stringify({ mcpServers: { hollware: {
|
|
144
|
+
type: "http",
|
|
145
|
+
url: `${url}/mcp`,
|
|
146
|
+
headers: { Authorization: `Bearer \${${TOKEN_VARIABLE}}` }
|
|
147
|
+
} } }, null, 2);
|
|
148
|
+
}
|
|
149
|
+
/** The `claude mcp add` line that names HOLLWARE_TOKEN instead of the token. */
|
|
150
|
+
function claudeAddLine(url) {
|
|
151
|
+
return `claude mcp add --transport http hollware ${url}/mcp --header 'Authorization: Bearer \${${TOKEN_VARIABLE}}'`;
|
|
152
|
+
}
|
|
153
|
+
/** What `hollware init --json` prints, as far as this package reads it. */
|
|
154
|
+
function readInitJson(stdout) {
|
|
155
|
+
try {
|
|
156
|
+
const parsed = JSON.parse(stdout);
|
|
157
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return Object.fromEntries(Object.entries(parsed));
|
|
158
|
+
} catch {}
|
|
159
|
+
throw new CreateError(`"hollware init --json" did not answer with a JSON object:\n${stdout}`);
|
|
160
|
+
}
|
|
130
161
|
/** Makes the project, and everything it needs to run locally. */
|
|
131
162
|
async function create(argv, io) {
|
|
132
163
|
const choices = readChoices(argv);
|
|
133
164
|
const target = resolve(io.cwd, choices.dir);
|
|
134
165
|
if (existsSync(target) && readdirSync(target).length > 0) throw new CreateError(`${target} is not empty: choose a new directory.`);
|
|
166
|
+
const tokenFile = choices.tokenFile === void 0 ? void 0 : resolve(io.cwd, choices.tokenFile);
|
|
167
|
+
if (tokenFile !== void 0 && existsSync(tokenFile)) throw new CreateError(`${tokenFile} exists already, and a token is never written over a file: choose another --token-file.`);
|
|
168
|
+
const say = (message) => {
|
|
169
|
+
if (!choices.json) io.out(message);
|
|
170
|
+
};
|
|
135
171
|
writeProject(io, target, choices);
|
|
136
|
-
|
|
172
|
+
say(`Created a Hollware project in ${target}.`);
|
|
173
|
+
if (tokenFile !== void 0) {
|
|
174
|
+
const inside = relative(target, tokenFile);
|
|
175
|
+
if (inside !== "" && !inside.startsWith("..") && !isAbsolute(inside)) appendFileSync(join(target, ".gitignore"), `\n# The token of "hollware init": never commit it.\n/${inside}\n`);
|
|
176
|
+
}
|
|
137
177
|
if (choices.git) await step(io, "git init", "git", ["init", "--quiet"], target);
|
|
178
|
+
const initArgs = [
|
|
179
|
+
"init",
|
|
180
|
+
"--site",
|
|
181
|
+
choices.site,
|
|
182
|
+
...choices.initFlags,
|
|
183
|
+
...tokenFile === void 0 ? [] : ["--token-file", tokenFile]
|
|
184
|
+
];
|
|
138
185
|
if (!choices.install) {
|
|
186
|
+
const next = [
|
|
187
|
+
`cd ${choices.dir}`,
|
|
188
|
+
`${choices.pm} install`,
|
|
189
|
+
`${choices.pm} exec hollware migrate --local`,
|
|
190
|
+
`${choices.pm} exec hollware ${initArgs.join(" ")} --local`
|
|
191
|
+
];
|
|
192
|
+
if (choices.json) {
|
|
193
|
+
io.out(JSON.stringify({
|
|
194
|
+
dir: target,
|
|
195
|
+
installed: false,
|
|
196
|
+
next
|
|
197
|
+
}));
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
139
200
|
io.out([
|
|
140
201
|
"",
|
|
141
202
|
"Next:",
|
|
142
|
-
`
|
|
143
|
-
` ${choices.pm} install`,
|
|
144
|
-
` ${choices.pm} exec hollware migrate --local`,
|
|
145
|
-
` ${choices.pm} exec hollware init --site ${[choices.site, ...choices.initFlags].join(" ")} --local`
|
|
203
|
+
...next.map((line) => ` ${line}`)
|
|
146
204
|
].join("\n"));
|
|
147
205
|
return 0;
|
|
148
206
|
}
|
|
149
|
-
|
|
207
|
+
say(`Installing with ${choices.pm} ...`);
|
|
150
208
|
await step(io, "The install", choices.pm, ["install"], target);
|
|
151
209
|
const hollware = join(target, "node_modules", ".bin", "hollware");
|
|
152
210
|
await step(io, "The migrations", hollware, ["migrate", "--local"], target);
|
|
153
211
|
const init = await step(io, "Making the site", hollware, [
|
|
154
|
-
|
|
155
|
-
"--
|
|
156
|
-
choices.
|
|
157
|
-
...choices.initFlags,
|
|
158
|
-
"--local"
|
|
212
|
+
...initArgs,
|
|
213
|
+
"--local",
|
|
214
|
+
...choices.json ? ["--json"] : []
|
|
159
215
|
], target);
|
|
216
|
+
const dev = `${choices.pm} exec hollware dev`;
|
|
217
|
+
if (choices.json) {
|
|
218
|
+
const made = readInitJson(init.stdout.trim());
|
|
219
|
+
const url = typeof made["mcpUrl"] === "string" ? made["mcpUrl"].replace(/\/mcp$/, "") : void 0;
|
|
220
|
+
io.out(JSON.stringify({
|
|
221
|
+
dir: target,
|
|
222
|
+
installed: true,
|
|
223
|
+
...made,
|
|
224
|
+
devCommand: dev,
|
|
225
|
+
adminUrl: url === void 0 ? null : `${url}/admin/`,
|
|
226
|
+
mcpConfig: url === void 0 ? null : {
|
|
227
|
+
variable: TOKEN_VARIABLE,
|
|
228
|
+
claudeCode: claudeAddLine(url),
|
|
229
|
+
json: JSON.parse(mcpJson(url))
|
|
230
|
+
}
|
|
231
|
+
}));
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
160
234
|
io.out(init.stdout.trim());
|
|
161
|
-
const token = TOKEN.exec(init.stdout)?.[0] ?? "<the token above>";
|
|
162
235
|
io.out([
|
|
163
236
|
"",
|
|
164
|
-
"Start it
|
|
237
|
+
"Start it:",
|
|
165
238
|
` cd ${choices.dir}`,
|
|
166
|
-
` ${
|
|
167
|
-
` claude mcp add --transport http hollware http://localhost:5199/mcp --header "Authorization: Bearer ${token}"`,
|
|
239
|
+
` ${dev}`,
|
|
168
240
|
"",
|
|
169
241
|
"The admin is at http://localhost:5199/admin/. To deploy, log in to Cloudflare and run:",
|
|
170
242
|
` ${choices.pm} exec wrangler login`,
|