@cparkerwebm/webmonterey 1.2.0 → 1.4.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/CHANGELOG.md +102 -0
- package/README.md +16 -0
- package/dist/webm.mjs +187 -44
- package/package.json +1 -1
- package/skills/launch/SKILL.md +17 -4
- package/skills/new-component/SKILL.md +6 -0
- package/skills/start/SKILL.md +65 -14
- package/skills/traps/SKILL.md +8 -0
- package/skills/webmaster/SKILL.md +118 -0
- package/src/cli/checks.test.ts +61 -0
- package/src/cli/checks.ts +43 -5
- package/src/cli/doctor.ts +78 -2
- package/src/cli/scaffold.test.ts +9 -0
- package/src/cli/scaffold.ts +12 -30
- package/src/cli/settings.ts +106 -0
- package/src/cli/sync.test.ts +62 -0
- package/src/cli/sync.ts +64 -1
- package/src/includes/webmonterey/config.test.ts +51 -0
- package/src/includes/webmonterey/config.ts +49 -0
- package/src/includes/webmonterey/copy-defaults.ts +9 -5
- package/src/includes/webmonterey/webmaster/webmaster.test.ts +61 -1
- package/src/includes/webmonterey/webmaster/webmaster.ts +57 -1
- package/src/integration/index.ts +45 -12
- package/src/integration/virtual.d.ts +17 -3
- package/src/layouts/base.astro +9 -6
- package/src/pages/robots.txt.ts +5 -3
- package/src/pages/webmaster.astro +50 -28
- package/template/site/CLAUDE.md +30 -3
package/src/integration/index.ts
CHANGED
|
@@ -31,7 +31,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
31
31
|
import { compileToCss } from '../design/compile.ts';
|
|
32
32
|
import { imageSize } from './image-size.ts';
|
|
33
33
|
import { loadForms, loadSiteFiles, resolveSiteUrl } from './config.ts';
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
APP_DIR,
|
|
36
|
+
appEnabled,
|
|
37
|
+
previewReason,
|
|
38
|
+
resolveAppPath,
|
|
39
|
+
} from '../includes/webmonterey/config.ts';
|
|
35
40
|
|
|
36
41
|
export interface WebmontereyOptions {
|
|
37
42
|
/**
|
|
@@ -79,10 +84,14 @@ export interface WebmontereyOptions {
|
|
|
79
84
|
/**
|
|
80
85
|
* The branch Workers Builds deploys to production. Default `main`.
|
|
81
86
|
*
|
|
82
|
-
* Every other branch is a PREVIEW
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
87
|
+
* Every other branch is a PREVIEW - and so is every build, on any branch and from any machine,
|
|
88
|
+
* of a site whose webmonterey.json says `environment: "staging"`. A preview build is different
|
|
89
|
+
* on purpose: every page is noindex with no canonical, there is no sitemap, robots.txt
|
|
90
|
+
* disallows everything, and Google Tag Manager does not load - so a client's review link can
|
|
91
|
+
* never be indexed, a site that has not launched cannot be indexed before it exists, and
|
|
92
|
+
* clicking around either never lands in their analytics. The branch comes from
|
|
93
|
+
* WORKERS_CI_BRANCH, which Workers Builds injects; the decision is `isPreviewBuild` in
|
|
94
|
+
* includes/webmonterey/config.ts.
|
|
86
95
|
*/
|
|
87
96
|
productionBranch?: string;
|
|
88
97
|
}
|
|
@@ -133,14 +142,29 @@ export default function webmonterey(options: WebmontereyOptions = {}): AstroInte
|
|
|
133
142
|
const appPath = resolveAppPath(files.site);
|
|
134
143
|
|
|
135
144
|
/*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
145
|
+
* PREVIEW OR PRODUCTION, decided in one place - previewReason - from two signals. A site
|
|
146
|
+
* whose webmonterey.json says `environment: "staging"` is a preview in every build,
|
|
147
|
+
* whatever the branch and whatever the machine; and on a launched site any Workers
|
|
148
|
+
* Builds branch other than the production one is a preview too. A local build of a
|
|
149
|
+
* production site has no branch and is production output, which is what
|
|
150
|
+
* `npm run preview` and the e2e need. See the option, and the function.
|
|
139
151
|
*/
|
|
140
152
|
const branch = process.env.WORKERS_CI_BRANCH ?? null;
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
const productionBranch = options.productionBranch ?? 'main';
|
|
154
|
+
const reason = previewReason({
|
|
155
|
+
environment: files.site.environment,
|
|
156
|
+
branch,
|
|
157
|
+
productionBranch,
|
|
158
|
+
});
|
|
159
|
+
const preview = reason !== null;
|
|
160
|
+
if (reason === 'staging') {
|
|
161
|
+
logger.info(
|
|
162
|
+
'environment is "staging" in webmonterey.json: a preview build - noindex, no sitemap, no analytics',
|
|
163
|
+
);
|
|
164
|
+
} else if (reason === 'branch') {
|
|
165
|
+
logger.info(
|
|
166
|
+
`branch "${branch}" is not ${productionBranch}: a preview build - noindex, no sitemap, no analytics`,
|
|
167
|
+
);
|
|
144
168
|
}
|
|
145
169
|
|
|
146
170
|
/*
|
|
@@ -255,7 +279,7 @@ export default function webmonterey(options: WebmontereyOptions = {}): AstroInte
|
|
|
255
279
|
load(id: string) {
|
|
256
280
|
switch (id) {
|
|
257
281
|
case resolved(VIRTUAL.build):
|
|
258
|
-
return `export default ${JSON.stringify({ preview, branch })};`;
|
|
282
|
+
return `export default ${JSON.stringify({ preview, reason, branch })};`;
|
|
259
283
|
case resolved(VIRTUAL.site):
|
|
260
284
|
return `export default ${JSON.stringify(files.site)};`;
|
|
261
285
|
case resolved(VIRTUAL.design):
|
|
@@ -363,6 +387,15 @@ export default function webmonterey(options: WebmontereyOptions = {}): AstroInte
|
|
|
363
387
|
* router's plain <h1>; a site that does not keeps the <h1>.
|
|
364
388
|
*/
|
|
365
389
|
`export const pageHeader = mod.pageHeader ?? null;`,
|
|
390
|
+
/*
|
|
391
|
+
* THE /webmaster PAGE'S BODY. The route, the copy, the share image and
|
|
392
|
+
* the agency graph stay the package's; a site whose document pages use
|
|
393
|
+
* a richer layout than an <h1> and a stack of paragraphs hands over the
|
|
394
|
+
* layout only. Without this a site's own src/pages/webmaster.astro
|
|
395
|
+
* collides with the injected route, and on one site the injected route
|
|
396
|
+
* won.
|
|
397
|
+
*/
|
|
398
|
+
`export const webmasterPage = mod.webmasterPage ?? null;`,
|
|
366
399
|
/*
|
|
367
400
|
* THE SITE'S JSON-LD, rendered into <head> on every route. The package
|
|
368
401
|
* emits none of its own: what a business claims about itself is the
|
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
* a package cannot import relatively - see includes/webmonterey/config.ts.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
* What this build is FOR. `preview` is true
|
|
9
|
-
*
|
|
8
|
+
* What this build is FOR. `preview` is true when webmonterey.json says `environment: "staging"`,
|
|
9
|
+
* or on any Workers Builds branch other than the production one - every page noindex, no
|
|
10
|
+
* sitemap, no analytics. `reason` says which signal decided it. A local build of a production
|
|
11
|
+
* site is not a preview. See `isPreviewBuild` in includes/webmonterey/config.ts.
|
|
10
12
|
*/
|
|
11
13
|
declare module 'virtual:webm/build' {
|
|
12
|
-
|
|
14
|
+
import type { PreviewReason } from '../includes/webmonterey/config.ts';
|
|
15
|
+
const build: { preview: boolean; reason: PreviewReason; branch: string | null };
|
|
13
16
|
export default build;
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -83,6 +86,17 @@ declare module 'virtual:webm/registry' {
|
|
|
83
86
|
* every later fix to block lookup, unknown-type reporting and FAQ extraction.
|
|
84
87
|
*/
|
|
85
88
|
export const pageHeader: AstroComponentFactory | null;
|
|
89
|
+
/**
|
|
90
|
+
* The body of the `/webmaster` page, when the site wants it laid out like its own document
|
|
91
|
+
* pages rather than as the package's `<h1>` and stack of paragraphs.
|
|
92
|
+
*
|
|
93
|
+
* Receives `WebmasterPageProps` from `includes/webmonterey/webmaster/webmaster.ts`:
|
|
94
|
+
* `{ title, description, intro, body }`, the merged `copy.webmaster` - `intro` and `body` as
|
|
95
|
+
* HTML through the inline prose renderer, the agency link already in `intro`. The component
|
|
96
|
+
* lays the words out and carries no copy of its own. The route, the `<head>`, the share image and the JSON-LD stay the package's.
|
|
97
|
+
* Null means the built-in layout, `pageHeader` seam included.
|
|
98
|
+
*/
|
|
99
|
+
export const webmasterPage: AstroComponentFactory | null;
|
|
86
100
|
/**
|
|
87
101
|
* The site's JSON-LD, rendered into <head> on every indexable route. The package emits none of
|
|
88
102
|
* its own; this component composes a graph from the builders in
|
package/src/layouts/base.astro
CHANGED
|
@@ -183,12 +183,15 @@ const {
|
|
|
183
183
|
} = Astro.props;
|
|
184
184
|
|
|
185
185
|
/*
|
|
186
|
-
* A
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
186
|
+
* A PREVIEW BUILD IS NOINDEX, EVERY PAGE. A preview is a staging site (`environment` in
|
|
187
|
+
* webmonterey.json) on any hostname, or a non-production branch of a launched one - see
|
|
188
|
+
* isPreviewBuild in includes/webmonterey/config.ts. The review link a client gets is a public
|
|
189
|
+
* workers.dev URL, and a search engine that finds one indexes a duplicate of the site under the
|
|
190
|
+
* wrong hostname - or, for a site that has not launched, indexes the site before it exists.
|
|
191
|
+
* noindex also suppresses the canonical and og:url below, so the preview sends one signal rather
|
|
192
|
+
* than "do not index me" beside "my real address is over there". GTM is skipped on a preview for
|
|
193
|
+
* the same reason in the other direction: a client clicking through their review link must not
|
|
194
|
+
* show up in their own analytics.
|
|
192
195
|
*/
|
|
193
196
|
const noindex = noindexProp || build.preview;
|
|
194
197
|
const analyticsOn = analytics && !build.preview;
|
package/src/pages/robots.txt.ts
CHANGED
|
@@ -25,9 +25,11 @@ import build from 'virtual:webm/build';
|
|
|
25
25
|
|
|
26
26
|
export const GET: APIRoute = ({ site }) => {
|
|
27
27
|
/*
|
|
28
|
-
* A
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* A PREVIEW DISALLOWS EVERYTHING - a staging site on any hostname, or a non-production branch
|
|
29
|
+
* of a launched one (isPreviewBuild in includes/webmonterey/config.ts). Every page on a preview
|
|
30
|
+
* is already noindex; this is the belt to that brace, and it is the one place Disallow is
|
|
31
|
+
* right - there is nothing on a preview a crawler should ever fetch, and no noindex tag it
|
|
32
|
+
* needs to see.
|
|
31
33
|
*/
|
|
32
34
|
if (build.preview) {
|
|
33
35
|
return new Response('User-agent: *\nDisallow: /\n', {
|
|
@@ -27,17 +27,25 @@ import { copy } from '../includes/webmonterey/copy.ts';
|
|
|
27
27
|
import {
|
|
28
28
|
AGENCY,
|
|
29
29
|
creditUrl,
|
|
30
|
+
webmasterPageProps,
|
|
30
31
|
WEBMASTER_OG_PATH,
|
|
31
32
|
} from '../includes/webmonterey/webmaster/webmaster.ts';
|
|
32
33
|
import { domain, hasDomain } from '../includes/webmonterey/site.ts';
|
|
33
34
|
import { compact, renderJsonLd } from '../includes/webmonterey/structured-data/nodes.ts';
|
|
34
|
-
import { pageHeader } from 'virtual:webm/registry';
|
|
35
|
+
import { pageHeader, webmasterPage } from 'virtual:webm/registry';
|
|
35
36
|
import ogImage from 'virtual:webm/webmaster-og';
|
|
36
37
|
import build from 'virtual:webm/build';
|
|
37
38
|
|
|
38
39
|
const PageHeader = pageHeader;
|
|
40
|
+
const WebmasterPage = webmasterPage;
|
|
39
41
|
const text = copy.webmaster;
|
|
40
42
|
const agencyHref = hasDomain ? creditUrl(domain, 'website') : AGENCY.url;
|
|
43
|
+
/*
|
|
44
|
+
* The words, resolved once for both layouts: the intro with the agency link in it, every
|
|
45
|
+
* paragraph through the inline prose renderer, so `**bold**` in copy.webmaster works here
|
|
46
|
+
* exactly as it does in page JSON.
|
|
47
|
+
*/
|
|
48
|
+
const props = webmasterPageProps(text, agencyHref);
|
|
41
49
|
|
|
42
50
|
/*
|
|
43
51
|
* The graph needs a real origin for stable @id values, which is the same gate the canonical tag
|
|
@@ -85,37 +93,51 @@ if (Astro.site && !build.preview) {
|
|
|
85
93
|
|
|
86
94
|
{
|
|
87
95
|
/*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
96
|
+
* THE BODY IS THE SITE'S IF IT WANTS IT. A site whose document pages - privacy, terms - use a
|
|
97
|
+
* richer layout than an <h1> and a stack of paragraphs exports `webmasterPage` from its
|
|
98
|
+
* registry and gets the merged copy handed to it: the same title, description and paragraphs
|
|
99
|
+
* as below, with the agency link already resolved into `intro`. Everything above this
|
|
100
|
+
* expression - the route, the <head>, the share image, the agency graph - stays the
|
|
101
|
+
* package's, so the page reads the same on every site and looks like the one it is on.
|
|
90
102
|
*/
|
|
91
|
-
|
|
92
|
-
<
|
|
103
|
+
WebmasterPage ? (
|
|
104
|
+
<WebmasterPage {...props} />
|
|
93
105
|
) : (
|
|
94
|
-
<
|
|
95
|
-
<div class="webm-container" data-width="text">
|
|
96
|
-
<h1>{text.title}</h1>
|
|
97
|
-
</div>
|
|
98
|
-
</section>
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
<section class="webm-section" data-space="lg">
|
|
103
|
-
<div class="webm-container" data-width="text">
|
|
104
|
-
<div class="webm-stack">
|
|
106
|
+
<Fragment>
|
|
105
107
|
{
|
|
106
108
|
/*
|
|
107
|
-
* The
|
|
108
|
-
*
|
|
109
|
-
* site; noopener without noreferrer, because the referrer is the attribution.
|
|
109
|
+
* The site's page header if it declares one, so this page looks like every other page
|
|
110
|
+
* on the site; the router's plain <h1> otherwise. Same fallback the block router uses.
|
|
110
111
|
*/
|
|
112
|
+
PageHeader ? (
|
|
113
|
+
<PageHeader title={text.title} description={text.description} />
|
|
114
|
+
) : (
|
|
115
|
+
<section class="webm-section" data-space="sm">
|
|
116
|
+
<div class="webm-container" data-width="text">
|
|
117
|
+
<h1>{text.title}</h1>
|
|
118
|
+
</div>
|
|
119
|
+
</section>
|
|
120
|
+
)
|
|
111
121
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
|
|
123
|
+
<section class="webm-section" data-space="lg">
|
|
124
|
+
<div class="webm-container" data-width="text">
|
|
125
|
+
<div class="webm-stack">
|
|
126
|
+
{/*
|
|
127
|
+
* The agency is named as a link in the first paragraph - the one outbound link
|
|
128
|
+
* on the site, followed, with the UTM parameters. Opens in a new tab because it
|
|
129
|
+
* leaves the site; noopener without noreferrer, because the referrer is the
|
|
130
|
+
* attribution. See introHtml in webmaster.ts - the same string a site's own
|
|
131
|
+
* layout receives, so the two cannot disagree about the link.
|
|
132
|
+
*/}
|
|
133
|
+
<p set:html={props.intro} />
|
|
134
|
+
{props.body.map((paragraph) => (
|
|
135
|
+
<p set:html={paragraph} />
|
|
136
|
+
))}
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</section>
|
|
140
|
+
</Fragment>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
121
143
|
</Base>
|
package/template/site/CLAUDE.md
CHANGED
|
@@ -14,7 +14,8 @@ The relationship is a WordPress parent theme and child theme.
|
|
|
14
14
|
|
|
15
15
|
**A fix to the package reaches this site on `npm update`.** That is the entire point of the
|
|
16
16
|
package existing, and it is why the default answer to "the shared behavior is wrong" is to fix
|
|
17
|
-
it upstream rather than to work around it here.
|
|
17
|
+
it upstream rather than to work around it here. Upstream means the package's own repo, in a
|
|
18
|
+
session opened there — never from here. Rule 12 says what a session in this repo does instead.
|
|
18
19
|
|
|
19
20
|
## The one rule that protects that
|
|
20
21
|
|
|
@@ -198,7 +199,29 @@ alone renders.
|
|
|
198
199
|
### 11. Never edit inside `node_modules`
|
|
199
200
|
|
|
200
201
|
A change there survives until the next install and not one second longer. If the package is
|
|
201
|
-
wrong,
|
|
202
|
+
wrong, the package gets fixed — by a session in the package repo, not this one. See rule 12.
|
|
203
|
+
|
|
204
|
+
### 12. A session in a client repo never edits the package
|
|
205
|
+
|
|
206
|
+
Not in `node_modules`, and not in the package's own checkout if it happens to be on this machine.
|
|
207
|
+
When something in the package is wrong, the deliverable from a session in this repo is a
|
|
208
|
+
**description of the fix** — what is wrong, where (file and line in the installed package), what
|
|
209
|
+
the behavior should be, and how to verify it — written as a prompt the user can run in a session
|
|
210
|
+
opened in the package repo. The package is versioned, tested and published on its own; this site
|
|
211
|
+
takes the fix with `npm update`.
|
|
212
|
+
|
|
213
|
+
This session stays inside this site's scope. An override in `design.json`, `src/styles/custom/`
|
|
214
|
+
or `src/actions/index.ts` is fine; reaching upstream is not.
|
|
215
|
+
|
|
216
|
+
Why this is a rule: "fix it upstream", read from inside a client repo, invited exactly the wrong
|
|
217
|
+
thing. The package source sat next door on the same machine, a session opened it mid-task and
|
|
218
|
+
edited it — a change nobody reviewed, in a repo nobody had open, on no branch, which then had to
|
|
219
|
+
be published before this site could even use it. The client-site session ended with the site
|
|
220
|
+
depending on a package version that did not exist.
|
|
221
|
+
|
|
222
|
+
`.claude/settings.json` denies `Edit` under any `node_modules/`, so the first half is mechanical
|
|
223
|
+
and `webm sync` keeps it that way. Claude Code has no rule syntax for "any path outside this
|
|
224
|
+
project", so the second half is this paragraph.
|
|
202
225
|
|
|
203
226
|
## Structure
|
|
204
227
|
|
|
@@ -208,7 +231,10 @@ webmonterey.json domain, client name, environment, features.
|
|
|
208
231
|
src/
|
|
209
232
|
components/ EVERY visible component. The package ships none.
|
|
210
233
|
registry.ts maps a block `type` to its component. A block whose type is
|
|
211
|
-
not here renders as nothing, silently.
|
|
234
|
+
not here renders as nothing, silently. Also exports the chrome:
|
|
235
|
+
`header`, `footer`, `panels`, `pageHeader`, `structuredData`, and
|
|
236
|
+
`webmasterPage` - the body of /webmaster, laid out like this
|
|
237
|
+
site's document pages, with the package's copy handed in.
|
|
212
238
|
content/pages/*.json the words. One file per route; home.json is `/`.
|
|
213
239
|
forms/*.json form definitions. Filename is the form id.
|
|
214
240
|
actions/index.ts re-exports the package pipeline. Wrap to customize.
|
|
@@ -241,6 +267,7 @@ replaced wholesale. This client's own skills go beside it at `.claude/skills/<na
|
|
|
241
267
|
| `/webm:start` | standing a new site up: repo, Cloudflare resources, first deploy |
|
|
242
268
|
| `/webm:launch` | launch checklist: structured data, sending domain, secrets, DNS, cutover |
|
|
243
269
|
| `/webm:upgrade` | taking a new package version |
|
|
270
|
+
| `/webm:webmaster` | laying out the package's `/webmaster` page like this site's own pages |
|
|
244
271
|
|
|
245
272
|
Content edits: branch, edit `src/content/pages/*.json`, `npm run check`, push — the branch
|
|
246
273
|
preview is the client's review link. Merge when approved. Never change a block's `type`.
|