create-hollware 0.5.0 → 0.8.0
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 +10 -8
- package/dist/index.mjs +11 -5
- package/package.json +1 -1
- package/template/AGENTS.md +8 -7
- package/template/README.md +4 -4
- package/template/src/worker.ts +4 -4
- package/template/test/worker.workerd.test.ts +15 -4
- package/template/tsconfig.json +0 -1
- package/template/hollware.config.ts +0 -36
package/README.md
CHANGED
|
@@ -13,14 +13,16 @@ It copies the starter into `my-cms`, pins every Hollware package at its own vers
|
|
|
13
13
|
its owner and a first personal token. Then it prints the token once, a link to set up your passkey
|
|
14
14
|
for the admin, and the `claude mcp add` line that connects Claude Code to it.
|
|
15
15
|
|
|
16
|
-
| Option | What it does
|
|
17
|
-
| ------------------ |
|
|
18
|
-
| `--name <name>` | The name of the Worker and of its database (default: the dir)
|
|
19
|
-
| `--site <key>` | The key of your site
|
|
20
|
-
| `--
|
|
21
|
-
| `--
|
|
22
|
-
| `--
|
|
23
|
-
| `--
|
|
16
|
+
| Option | What it does |
|
|
17
|
+
| ------------------ | ----------------------------------------------------------------- |
|
|
18
|
+
| `--name <name>` | The name of the Worker and of its database (default: the dir) |
|
|
19
|
+
| `--site <key>` | The key of your site, such as my-site (default: my-site) |
|
|
20
|
+
| `--preset <name>` | What the site starts with: blog, docs or empty (default: blog) |
|
|
21
|
+
| `--locales <list>` | Its locales, comma-separated, the first the default (default: en) |
|
|
22
|
+
| `--pm <npm\|pnpm>` | The package manager that installs (default: npm) |
|
|
23
|
+
| `--yes` | Take the defaults; the directory is `my-hollware` |
|
|
24
|
+
| `--no-install` | Only write the files |
|
|
25
|
+
| `--no-git` | Do not run `git init` |
|
|
24
26
|
|
|
25
27
|
The directory must be new or empty. Node.js 24 or later.
|
|
26
28
|
|
package/dist/index.mjs
CHANGED
|
@@ -14,7 +14,9 @@ const USAGE = [
|
|
|
14
14
|
"",
|
|
15
15
|
"Options:",
|
|
16
16
|
" --name <name> The name of the Worker and of its database (default: the name of <dir>)",
|
|
17
|
-
" --site <key> The key of your site
|
|
17
|
+
" --site <key> The key of your site, such as my-site; it never changes (default: my-site)",
|
|
18
|
+
" --preset <name> What the site starts with: blog, docs or empty (default: blog)",
|
|
19
|
+
" --locales <list> Its locales, comma-separated, the first the default (default: en)",
|
|
18
20
|
" --pm <npm|pnpm> The package manager that installs (default: npm)",
|
|
19
21
|
" --yes Take the defaults: <dir> is my-hollware when it is not given",
|
|
20
22
|
" --no-install Do not install, migrate or make the site",
|
|
@@ -32,7 +34,7 @@ var CreateError = class extends Error {
|
|
|
32
34
|
};
|
|
33
35
|
/** A Worker's name: lowercase letters, digits and hyphens, as Cloudflare takes it. */
|
|
34
36
|
const WORKER_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
35
|
-
/** A site key, as
|
|
37
|
+
/** A site key, as a site's definition takes it (ADR-033). */
|
|
36
38
|
const SITE_KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
37
39
|
/** What `@hollware/*` and `hollware` say in the template's package.json. */
|
|
38
40
|
const VERSION_PLACEHOLDER = "HOLLWARE_VERSION";
|
|
@@ -47,6 +49,8 @@ function readChoices(argv) {
|
|
|
47
49
|
options: {
|
|
48
50
|
name: { type: "string" },
|
|
49
51
|
site: { type: "string" },
|
|
52
|
+
preset: { type: "string" },
|
|
53
|
+
locales: { type: "string" },
|
|
50
54
|
pm: { type: "string" },
|
|
51
55
|
yes: { type: "boolean" },
|
|
52
56
|
"no-install": { type: "boolean" },
|
|
@@ -73,7 +77,8 @@ function readChoices(argv) {
|
|
|
73
77
|
site,
|
|
74
78
|
pm,
|
|
75
79
|
install: values["no-install"] !== true,
|
|
76
|
-
git: values["no-git"] !== true
|
|
80
|
+
git: values["no-git"] !== true,
|
|
81
|
+
initFlags: [...values.preset === void 0 ? [] : ["--preset", values.preset], ...values.locales === void 0 ? [] : ["--locales", values.locales]]
|
|
77
82
|
};
|
|
78
83
|
}
|
|
79
84
|
function replaceIn(path, replacements) {
|
|
@@ -91,7 +96,7 @@ function writeProject(io, target, choices) {
|
|
|
91
96
|
["\"database_name\": \"my-hollware\"", `"database_name": "${choices.name}"`],
|
|
92
97
|
["hollware-events", `${choices.name}-events`]
|
|
93
98
|
]);
|
|
94
|
-
for (const file of ["
|
|
99
|
+
for (const file of ["README.md"]) replaceIn(join(target, file), [["my-site", choices.site]]);
|
|
95
100
|
if (choices.pm === "pnpm") writeFileSync(join(target, "pnpm-workspace.yaml"), PNPM_WORKSPACE);
|
|
96
101
|
}
|
|
97
102
|
/**
|
|
@@ -126,7 +131,7 @@ async function create(argv, io) {
|
|
|
126
131
|
` cd ${choices.dir}`,
|
|
127
132
|
` ${choices.pm} install`,
|
|
128
133
|
` ${choices.pm} exec hollware migrate --local`,
|
|
129
|
-
` ${choices.pm} exec hollware init --site ${choices.site} --local`
|
|
134
|
+
` ${choices.pm} exec hollware init --site ${[choices.site, ...choices.initFlags].join(" ")} --local`
|
|
130
135
|
].join("\n"));
|
|
131
136
|
return 0;
|
|
132
137
|
}
|
|
@@ -138,6 +143,7 @@ async function create(argv, io) {
|
|
|
138
143
|
"init",
|
|
139
144
|
"--site",
|
|
140
145
|
choices.site,
|
|
146
|
+
...choices.initFlags,
|
|
141
147
|
"--local"
|
|
142
148
|
], target);
|
|
143
149
|
io.out(init.stdout.trim());
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
This is a Hollware project: a headless CMS on Cloudflare Workers. The content
|
|
4
|
-
|
|
3
|
+
This is a Hollware project: a headless CMS on Cloudflare Workers. The content and the structure
|
|
4
|
+
of the sites live in D1; this repository holds only the Worker.
|
|
5
5
|
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
- The structure of a site (its collections, the JSON Schema of their frontmatter, routes, rules,
|
|
7
|
+
style guides) is data: read it with `get_structure`, change it with `update_site` or
|
|
8
|
+
`set_collection` (they need `schema:admin`), or in the admin at `/admin/`. It applies at once;
|
|
9
|
+
nothing here changes and nothing is deployed.
|
|
10
|
+
- `src/worker.ts` stays as it is: `createHollware()` is the whole Worker.
|
|
9
11
|
- Content is never edited in files here. Write it through the MCP tools (`create_draft`,
|
|
10
12
|
`update_draft`, `validate`, `publish`) of the Hollware at `HOLLWARE_PUBLIC_URL` + `/mcp`.
|
|
11
|
-
- Run `pnpm test` and `pnpm exec wrangler types --check` after a change of
|
|
12
|
-
`wrangler.jsonc`, and `pnpm exec hollware doctor` when something does not work.
|
|
13
|
+
- Run `pnpm test` and `pnpm exec wrangler types --check` after a change of `wrangler.jsonc`, and `pnpm exec hollware doctor` when something does not work.
|
|
13
14
|
- Never commit a token. `hollware init` and `hollware token create` print one once.
|
|
14
15
|
- Docs: https://hollware.com
|
package/template/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# My Hollware
|
|
2
2
|
|
|
3
3
|
A [Hollware](https://hollware.com) project: a headless CMS on Cloudflare Workers that your agents
|
|
4
|
-
write to over MCP.
|
|
4
|
+
write to over MCP. Your sites, their collections and their rules live in the database: shape them
|
|
5
|
+
in the admin, or let an agent do it, and every write is checked against them. No deploy.
|
|
5
6
|
|
|
6
7
|
## What is inside
|
|
7
8
|
|
|
8
9
|
| Path | What it is |
|
|
9
10
|
| --------------------------- | ---------------------------------------------------------------------------------- |
|
|
10
|
-
| `
|
|
11
|
-
| `src/worker.ts` | The Worker: `createHollware(config)` builds it |
|
|
11
|
+
| `src/worker.ts` | The Worker: `createHollware()` builds it |
|
|
12
12
|
| `wrangler.jsonc` | The Worker, its D1 database, its KV namespace, its queues and its public URL |
|
|
13
13
|
| `vite.config.ts` | Vite with the Cloudflare plugin, and the admin at `/admin/` |
|
|
14
14
|
| `vitest.config.ts`, `test/` | Tests that run inside workerd |
|
|
@@ -26,7 +26,7 @@ its owner and a first token. To do it again on a new local database:
|
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
28
|
pnpm exec hollware migrate --local
|
|
29
|
-
pnpm exec hollware init --site my-site --local
|
|
29
|
+
pnpm exec hollware init --site my-site --local # --preset blog|docs|empty --locales en,es
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
`init` prints a personal token once, and a link to set up your passkey for the admin. Give the
|
package/template/src/worker.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHollware } from '@hollware/server'
|
|
2
|
-
import config from '../hollware.config.ts'
|
|
3
2
|
|
|
4
|
-
// This file is the whole Worker
|
|
5
|
-
|
|
3
|
+
// This file is the whole Worker. Your sites, their collections and their rules live in the
|
|
4
|
+
// database: shape them in the admin at /admin/, or have an agent do it over MCP. No deploy.
|
|
5
|
+
const hollware = createHollware()
|
|
6
6
|
|
|
7
7
|
// MCP for your agents at /mcp, behind a personal token.
|
|
8
8
|
export default {
|
|
@@ -11,5 +11,5 @@ export default {
|
|
|
11
11
|
queue: hollware.queue,
|
|
12
12
|
} satisfies ExportedHandler<Env>
|
|
13
13
|
|
|
14
|
-
// What your sites read over a service binding:
|
|
14
|
+
// What your sites read over a service binding: read-only methods, no network, no token.
|
|
15
15
|
export class Hollware extends hollware.Hollware {}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import { sitePreset } from '@hollware/core'
|
|
1
2
|
import type { HollwareRpc } from '@hollware/server'
|
|
2
3
|
import { env, exports } from 'cloudflare:workers'
|
|
3
4
|
import { describe, expect, it } from 'vitest'
|
|
4
5
|
|
|
5
6
|
import worker from '../src/worker.ts'
|
|
6
7
|
|
|
7
|
-
// Fails when: src/worker.ts stops exporting `{ fetch: hollware.fetch }` of createHollware(
|
|
8
|
+
// Fails when: src/worker.ts stops exporting `{ fetch: hollware.fetch }` of createHollware()
|
|
8
9
|
// as the Worker, so that GET /health for the public URL of wrangler.jsonc (localhost:5199) stops
|
|
9
10
|
// answering 200, or another Host stops being a 403; when it stops exporting the class `Hollware`
|
|
10
|
-
// that sites read over a service binding, or
|
|
11
|
-
//
|
|
11
|
+
// that sites read over a service binding, or stops serving the sites of the database (ADR-033:
|
|
12
|
+
// describeSite must answer the site that `hollware init` makes); or when the D1 binding of wrangler.jsonc stops
|
|
12
13
|
// pointing `migrations_dir` at the migrations of @hollware/server, so that its tables are missing.
|
|
13
14
|
|
|
14
15
|
// A request made inside workerd has no Host header of its own: a browser or curl would send one.
|
|
@@ -45,7 +46,14 @@ describe('the Worker over HTTP', () => {
|
|
|
45
46
|
})
|
|
46
47
|
|
|
47
48
|
describe('the Hollware class', () => {
|
|
48
|
-
it('serves the site of
|
|
49
|
+
it('serves the site of the database over RPC, as hollware init makes it', async () => {
|
|
50
|
+
const definition = sitePreset('blog', ['en', 'es'], 'en')
|
|
51
|
+
await env.DB.prepare(
|
|
52
|
+
"INSERT INTO sites (id, definition, version, created_at, updated_at) VALUES ('my-site', ?1, 1, 'now', 'now')",
|
|
53
|
+
)
|
|
54
|
+
.bind(JSON.stringify(definition))
|
|
55
|
+
.run()
|
|
56
|
+
|
|
49
57
|
const answer = await rpc.describeSite('my-site')
|
|
50
58
|
|
|
51
59
|
expect(answer).toMatchObject({
|
|
@@ -87,11 +95,14 @@ describe('the database', () => {
|
|
|
87
95
|
'outbox',
|
|
88
96
|
'passkeys',
|
|
89
97
|
'publications',
|
|
98
|
+
'review_comments',
|
|
90
99
|
'reviews',
|
|
91
100
|
'revision_tags',
|
|
92
101
|
'revisions',
|
|
93
102
|
'schedules',
|
|
94
103
|
'sessions',
|
|
104
|
+
'site_changes',
|
|
105
|
+
'site_secrets',
|
|
95
106
|
'sites',
|
|
96
107
|
'tokens',
|
|
97
108
|
'users',
|
package/template/tsconfig.json
CHANGED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { defineCollection, defineConfig } from '@hollware/core'
|
|
2
|
-
import * as z from 'zod'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The config of your Hollware: one site with a small bilingual blog. Agents read it with
|
|
6
|
-
* `describe_site` before they write, so everything it says is what they follow: the shape of the
|
|
7
|
-
* frontmatter, the routes of an entry per locale, and the style guide.
|
|
8
|
-
*
|
|
9
|
-
* `my-site` is the key of the site. Create it in your database with `hollware init --site my-site`.
|
|
10
|
-
*/
|
|
11
|
-
const blog = defineCollection({
|
|
12
|
-
schema: z.object({
|
|
13
|
-
title: z.string().min(1),
|
|
14
|
-
description: z.string().min(1).max(160),
|
|
15
|
-
date: z.iso.date(),
|
|
16
|
-
tags: z.array(z.string().min(1)).min(1),
|
|
17
|
-
// The entries of one post in each language share this value.
|
|
18
|
-
translationGroup: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
19
|
-
}),
|
|
20
|
-
routes: { en: '/blog/:slug/', es: '/es/blog/:slug/' },
|
|
21
|
-
translations: { groupBy: 'translationGroup' },
|
|
22
|
-
tags: 'tags',
|
|
23
|
-
sortBy: 'date',
|
|
24
|
-
styleGuide: 'Short paragraphs, one idea per section, and an example for every claim.',
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
export default defineConfig({
|
|
28
|
-
sites: {
|
|
29
|
-
'my-site': {
|
|
30
|
-
locales: ['en', 'es'],
|
|
31
|
-
defaultLocale: 'en',
|
|
32
|
-
collections: { blog },
|
|
33
|
-
styleGuide: 'Plain language, with the reader in the second person.',
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
})
|