create-hollware 1.0.0-rc.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.
Files changed (2) hide show
  1. package/dist/index.mjs +91 -19
  2. 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
- io.out(`Created a Hollware project in ${target}.`);
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
- ` cd ${choices.dir}`,
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
- io.out(`Installing with ${choices.pm} ...`);
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
- "init",
155
- "--site",
156
- choices.site,
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, and connect your agent (Claude Code):",
237
+ "Start it:",
165
238
  ` cd ${choices.dir}`,
166
- ` ${choices.pm === "npm" ? "npm run dev" : "pnpm dev"}`,
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`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-hollware",
3
- "version": "1.0.0-rc.0",
3
+ "version": "1.0.0-rc.1",
4
4
  "description": "Create a new Hollware project.",
5
5
  "homepage": "https://hollware.com",
6
6
  "bugs": {