@cparkerwebm/webmonterey 1.2.0 → 1.3.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 +55 -0
- package/README.md +6 -0
- package/dist/webm.mjs +187 -44
- package/package.json +1 -1
- package/skills/launch/SKILL.md +12 -4
- package/skills/start/SKILL.md +63 -13
- package/skills/traps/SKILL.md +8 -0
- package/src/cli/checks.test.ts +40 -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 +61 -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/integration/index.ts +36 -12
- package/src/integration/virtual.d.ts +6 -3
- package/src/layouts/base.astro +9 -6
- package/src/pages/robots.txt.ts +5 -3
- package/template/site/CLAUDE.md +25 -2
|
@@ -3,9 +3,11 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import {
|
|
4
4
|
APP_DIR,
|
|
5
5
|
isConfigured,
|
|
6
|
+
isPreviewBuild,
|
|
6
7
|
isStagingDeployment,
|
|
7
8
|
isValidTimeZone,
|
|
8
9
|
PLACEHOLDER,
|
|
10
|
+
previewReason,
|
|
9
11
|
resolveAppPath,
|
|
10
12
|
resolveDisplayName,
|
|
11
13
|
workerFirstPaths,
|
|
@@ -92,3 +94,52 @@ test('shortName falls back to the display name when unset', () => {
|
|
|
92
94
|
assert.equal(isConfigured('FoML'), true);
|
|
93
95
|
assert.equal(isConfigured(''), false);
|
|
94
96
|
});
|
|
97
|
+
|
|
98
|
+
/* --- preview builds ------------------------------------------------------ */
|
|
99
|
+
|
|
100
|
+
test('a staging site is a preview with no branch at all - the laptop build', () => {
|
|
101
|
+
assert.equal(isPreviewBuild({ environment: 'staging', branch: null }), true);
|
|
102
|
+
assert.equal(previewReason({ environment: 'staging', branch: null }), 'staging');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('a staging site is a preview on main - the case that was crawlable', () => {
|
|
106
|
+
/* autire.webmonterey.workers.dev: main on Workers Builds, environment staging, and indexable
|
|
107
|
+
* because only a non-production BRANCH used to be a preview. */
|
|
108
|
+
assert.equal(isPreviewBuild({ environment: 'staging', branch: 'main' }), true);
|
|
109
|
+
assert.equal(previewReason({ environment: 'staging', branch: 'main' }), 'staging');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('a production site with no branch is production output', () => {
|
|
113
|
+
assert.equal(isPreviewBuild({ environment: 'production', branch: null }), false);
|
|
114
|
+
assert.equal(previewReason({ environment: 'production', branch: null }), null);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('a production site on main is production output', () => {
|
|
118
|
+
assert.equal(isPreviewBuild({ environment: 'production', branch: 'main' }), false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('a feature branch of a production site is still a preview', () => {
|
|
122
|
+
/* webmonterey.json is committed, so the branch inherits production from main. The branch rule
|
|
123
|
+
* is what keeps a launched site's review links out of the index. */
|
|
124
|
+
assert.equal(isPreviewBuild({ environment: 'production', branch: 'feature/x' }), true);
|
|
125
|
+
assert.equal(previewReason({ environment: 'production', branch: 'feature/x' }), 'branch');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('productionBranch renames which branch is production', () => {
|
|
129
|
+
const on = { environment: 'production', productionBranch: 'release' } as const;
|
|
130
|
+
assert.equal(isPreviewBuild({ ...on, branch: 'release' }), false);
|
|
131
|
+
assert.equal(isPreviewBuild({ ...on, branch: 'main' }), true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('staging wins over the production branch, whatever it is called', () => {
|
|
135
|
+
assert.equal(
|
|
136
|
+
previewReason({ environment: 'staging', branch: 'release', productionBranch: 'release' }),
|
|
137
|
+
'staging',
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('an unset environment is production, so a site predating the field builds as before', () => {
|
|
142
|
+
assert.equal(isPreviewBuild({ environment: undefined, branch: null }), false);
|
|
143
|
+
assert.equal(isPreviewBuild({ environment: undefined, branch: 'main' }), false);
|
|
144
|
+
assert.equal(isPreviewBuild({ environment: undefined, branch: 'feature/x' }), true);
|
|
145
|
+
});
|
|
@@ -125,6 +125,14 @@ export interface SiteConfig {
|
|
|
125
125
|
* Read it anywhere via `environment`, `isStaging` and `isProduction` from webmonterey/site.
|
|
126
126
|
* The first consumer is transactional email, which redirects every recipient to `stagingEmail`
|
|
127
127
|
* on a staging deployment rather than mailing the client's real contacts from a preview.
|
|
128
|
+
*
|
|
129
|
+
* THE SECOND CONSUMER IS INDEXABILITY. A staging site is a PREVIEW BUILD on every hostname and
|
|
130
|
+
* in every build - laptop, `main` on Workers Builds, anywhere: every page is noindex with no
|
|
131
|
+
* canonical, there is no sitemap, robots.txt disallows everything, and Google Tag Manager does
|
|
132
|
+
* not load. Until this switch existed a site that had not launched was crawlable on its
|
|
133
|
+
* workers.dev URL the moment `main` deployed, because only a non-production BRANCH was a
|
|
134
|
+
* preview. See `isPreviewBuild`. Flipping this to production is therefore what makes a site
|
|
135
|
+
* indexable, which is why /webm:launch does it only once the custom domain is live.
|
|
128
136
|
*/
|
|
129
137
|
environment?: 'production' | 'staging';
|
|
130
138
|
|
|
@@ -344,3 +352,44 @@ export function isStagingDeployment(
|
|
|
344
352
|
/* Match the label, not a substring: a client domain ending "notworkers.dev" is not a preview. */
|
|
345
353
|
return Boolean(hostname && /(^|\.)workers\.dev$/i.test(hostname));
|
|
346
354
|
}
|
|
355
|
+
|
|
356
|
+
/** Which signal made a build a preview, or null for production output. */
|
|
357
|
+
export type PreviewReason = 'staging' | 'branch' | null;
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Why this build is a preview - noindex on every page, no canonical, no sitemap, robots.txt
|
|
361
|
+
* disallowing everything, no analytics - or null when it is production output.
|
|
362
|
+
*
|
|
363
|
+
* TWO INDEPENDENT SIGNALS, for the same reason `isStagingDeployment` has two.
|
|
364
|
+
*
|
|
365
|
+
* `environment` is what the deployment is FOR. A site that has not launched says `staging`, and
|
|
366
|
+
* a site that has not launched must not be indexable ANYWHERE: not on a feature branch, not on
|
|
367
|
+
* `main`, not from a laptop. Before this signal existed only a non-production branch was a
|
|
368
|
+
* preview, so `main` on Workers Builds was production output for every site that had not gone
|
|
369
|
+
* live yet - indexable pages with a canonical, a sitemap and `Allow: /` on a public workers.dev
|
|
370
|
+
* hostname. autire.webmonterey.workers.dev was crawlable that way.
|
|
371
|
+
*
|
|
372
|
+
* The branch covers the opposite case: webmonterey.json is committed, so a feature branch of a
|
|
373
|
+
* LAUNCHED site inherits `production` from main and would build indexable pages under a review
|
|
374
|
+
* URL. Workers Builds injects WORKERS_CI_BRANCH; anything other than the production branch is a
|
|
375
|
+
* preview whatever the config says.
|
|
376
|
+
*
|
|
377
|
+
* An unset environment is production, as it is everywhere else - see SiteConfig.environment for
|
|
378
|
+
* why that default is the safe one - so a site predating the field builds exactly as before.
|
|
379
|
+
* `branch` is null when nothing injected one, which is every laptop build.
|
|
380
|
+
*/
|
|
381
|
+
export function previewReason(input: {
|
|
382
|
+
environment: SiteConfig['environment'] | undefined;
|
|
383
|
+
branch: string | null | undefined;
|
|
384
|
+
productionBranch?: string;
|
|
385
|
+
}): PreviewReason {
|
|
386
|
+
if (input.environment === 'staging') return 'staging';
|
|
387
|
+
const branch = input.branch ?? null;
|
|
388
|
+
if (branch !== null && branch !== (input.productionBranch ?? 'main')) return 'branch';
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Whether this build is a preview. `previewReason` says which signal decided it. */
|
|
393
|
+
export function isPreviewBuild(input: Parameters<typeof previewReason>[0]): boolean {
|
|
394
|
+
return previewReason(input) !== null;
|
|
395
|
+
}
|
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):
|
|
@@ -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
|
|
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', {
|
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
|
|