@saastemly/voidcommerce 0.4.0 → 0.6.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/src/manifest.ts CHANGED
@@ -30,9 +30,10 @@ export const MANIFEST_FILE = "voidcommerce.json";
30
30
  * strict EXPERIMENTAL. A monorepo turned inside out. The REPOSITORY
31
31
  * ROOT is the storefront — its package.json, its pages, yours to
32
32
  * edit — and the worker is generated underneath it at .vc/app
33
- * from the manifest, gitignored, never hand-edited. Hosted the
34
- * way a monorepo is: the worker on api.<domain>, the storefront
35
- * on <domain>.
33
+ * from the manifest, gitignored, never hand-edited. ONE worker
34
+ * serves both at <domain>: the storefront's prerendered tree is
35
+ * folded into the worker's assets at build time, so there is one
36
+ * origin, one deploy, and one certificate Cloudflare issues.
36
37
  *
37
38
  * The first question, because it decides where `void init` runs and what
38
39
  * the domain means. It cannot change after init: moving files is not a
@@ -54,7 +55,7 @@ export const LAYOUTS: Array<{ id: Layout; label: string; hint: string }> = [
54
55
  {
55
56
  id: "strict",
56
57
  label: "Strict (experimental)",
57
- hint: "the root IS the storefront, sharing one package.json; the worker is generated under .vc/app from the manifest and never hand-edited",
58
+ hint: "the root IS the storefront, sharing one package.json; the worker is generated under .vc/app and serves the storefront from its own assets — one origin, one deploy",
58
59
  },
59
60
  ];
60
61
 
@@ -68,9 +69,28 @@ export const LAYOUTS: Array<{ id: Layout; label: string; hint: string }> = [
68
69
  */
69
70
  export const isSingleApp = (layout: Layout): boolean => layout === "app";
70
71
 
71
- /** Does this layout carry a storefront of its own, and so a second origin to trust? */
72
+ /** Does this layout carry a storefront of its own, in the repository? */
72
73
  export const hasFrontend = (layout: Layout): boolean => layout !== "app";
73
74
 
75
+ /**
76
+ * Does ONE worker serve both the storefront and the API?
77
+ *
78
+ * `app` always did — it generates its storefront into the worker. `strict`
79
+ * now does too: the root's prerendered tree is merged into the worker's own
80
+ * assets directory at build time, so there is one origin, one hostname, one
81
+ * deploy, and one certificate that Cloudflare issues itself.
82
+ *
83
+ * That deletes the whole GitHub Pages half — the A/AAAA records, the CNAME
84
+ * file, the base-path rewriting — and with it a trap worth naming: GitHub
85
+ * treats any Cloudflare IP as ineligible for a certificate, so a proxied
86
+ * record silently stops issuance AND renewal about ninety days later, while
87
+ * GitHub's own health check still reports green.
88
+ *
89
+ * `monorepo` keeps the split, because there the frontend is a separate app
90
+ * that a person may well want hosted separately.
91
+ */
92
+ export const oneOrigin = (layout: Layout): boolean => layout !== "monorepo";
93
+
74
94
  /**
75
95
  * Public suffixes whose second label is not a registrable name, so
76
96
  * `shop.example.co.uk` is a subdomain of `example.co.uk`, not of `co.uk`.
@@ -100,7 +120,9 @@ export function isApex(manifest: Manifest): boolean {
100
120
  /** Every hostname the worker answers on — the wrangler routes, in order. */
101
121
  export function workerHosts(manifest: Manifest): string[] {
102
122
  const { domain } = manifest.shop;
103
- if (!isSingleApp(manifest.layout)) return [`api.${domain}`];
123
+ // Only a monorepo puts the worker on its own hostname; everything else
124
+ // serves the shop and its API from one origin.
125
+ if (!oneOrigin(manifest.layout)) return [`api.${domain}`];
104
126
  return isApex(manifest) ? [domain, `www.${domain}`] : [domain];
105
127
  }
106
128
 
@@ -145,7 +167,10 @@ export interface Manifest {
145
167
  * default that is safe to assume.
146
168
  */
147
169
  taxRegistered?: boolean | undefined;
148
- /** `saastemly.github.io` — the monorepo's frontend lives on GitHub Pages; the www record points here. */
170
+ /**
171
+ * `saastemly.github.io` — only a MONOREPO has a storefront on GitHub
172
+ * Pages. In `app` and `strict` the worker serves it, so this is unused.
173
+ */
149
174
  pagesHost?: string | undefined;
150
175
  };
151
176
  /** Chosen ids from each group, keyed by group id. */
@@ -296,7 +321,7 @@ export function validate(manifest: Manifest): string[] {
296
321
  `DNS at Simply.com cannot host ${workerHosts(manifest)[0]} — a Worker custom domain is created by Cloudflare, in a zone on Cloudflare`,
297
322
  );
298
323
  }
299
- if (hasFrontend(manifest.layout) && manifest.shop.pagesHost && !/^[a-z0-9-]+\.github\.io$/i.test(manifest.shop.pagesHost)) {
324
+ if (!oneOrigin(manifest.layout) && manifest.shop.pagesHost && !/^[a-z0-9-]+\.github\.io$/i.test(manifest.shop.pagesHost)) {
300
325
  problems.push(`Pages host "${manifest.shop.pagesHost}" should be <owner>.github.io`);
301
326
  }
302
327
  return problems;
package/src/regenerate.ts CHANGED
@@ -30,6 +30,7 @@ export async function generateCommand(): Promise<number> {
30
30
  [
31
31
  ...result.written.map((file) => `${color.green("+")} ${file}`),
32
32
  ...result.kept.map((file) => `${color.dim("=")} ${file} ${color.dim("(kept)")}`),
33
+ ...result.retired.map((file) => `${color.red("-")} ${file} ${color.dim("(retired — no longer generated)")}`),
33
34
  ].join("\n"),
34
35
  );
35
36
  return project.manifest.layout === "strict" ? finishStrict(project.root) : 0;
package/src/wizard.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as p from "@clack/prompts";
2
2
  import color from "picocolors";
3
3
  import { GROUPS, type Group } from "./catalog";
4
- import { LAYOUTS, type Layout, type Manifest, zoneOf } from "./manifest";
4
+ import { LAYOUTS, type Layout, type Manifest, zoneOf , oneOrigin} from "./manifest";
5
5
 
6
6
  /**
7
7
  * The forms.
@@ -98,6 +98,10 @@ async function askGroup(group: Group, previous: string[] | undefined, alsoRequir
98
98
  export async function runWizard(existing: Manifest | null, layout: Layout): Promise<Manifest> {
99
99
  const monorepo = layout === "monorepo";
100
100
  const frontend = layout !== "app";
101
+ // Only a monorepo publishes its storefront to GitHub Pages. In `strict`
102
+ // the worker serves it from its own assets, so there is no Pages host to
103
+ // ask for — and asking would imply a second origin that does not exist.
104
+ const onPages = !oneOrigin(layout);
101
105
 
102
106
  const shop = unwrap(
103
107
  await p.group(
@@ -171,7 +175,7 @@ export async function runWizard(existing: Manifest | null, layout: Layout): Prom
171
175
  initialValue: existing?.shop.locale ?? "",
172
176
  validate: (value) => (/^[a-z]{2}(-[A-Z]{2})?$/.test(value.trim()) ? undefined : "like da, or en-GB."),
173
177
  }),
174
- ...(frontend
178
+ ...(onPages
175
179
  ? {
176
180
  pagesHost: () =>
177
181
  p.text({
@@ -211,7 +215,7 @@ export async function runWizard(existing: Manifest | null, layout: Layout): Prom
211
215
  locale: shop.locale.trim(),
212
216
  taxRegistered: (shop as { taxRegistered?: string }).taxRegistered === "yes",
213
217
  zone: (shop as { zone?: string }).zone?.trim().toLowerCase() || undefined,
214
- pagesHost: frontend ? (shop as { pagesHost?: string }).pagesHost?.trim().toLowerCase() || undefined : undefined,
218
+ pagesHost: onPages ? (shop as { pagesHost?: string }).pagesHost?.trim().toLowerCase() || undefined : undefined,
215
219
  },
216
220
  chosen,
217
221
  };