@morphemecms/cms 0.3.0 → 0.4.0-next-20261104164754

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 (52) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/bin/morpheme.mjs +1 -1
  4. package/defaults/editor.ts +3 -1
  5. package/lib/changelog.d.mts +24 -0
  6. package/lib/changelog.mjs +65 -0
  7. package/lib/changelog.test.d.mts +1 -0
  8. package/lib/changelog.test.mjs +67 -0
  9. package/lib/cms-version.d.mts +56 -0
  10. package/lib/cms-version.mjs +85 -0
  11. package/lib/cms-version.test.d.mts +1 -0
  12. package/lib/cms-version.test.mjs +68 -0
  13. package/lib/lockfile.d.mts +19 -0
  14. package/lib/lockfile.mjs +135 -0
  15. package/lib/lockfile.test.d.mts +1 -0
  16. package/lib/lockfile.test.mjs +88 -0
  17. package/lib/range.d.mts +4 -0
  18. package/lib/range.mjs +104 -0
  19. package/lib/range.test.d.mts +1 -0
  20. package/lib/range.test.mjs +46 -0
  21. package/package.json +2 -1
  22. package/scripts/built-site.mjs +14 -0
  23. package/scripts/check-cms-update.mjs +405 -0
  24. package/site/theme.d.mts +1 -1
  25. package/site/theme.mjs +2 -2
  26. package/src/App.tsx +3 -0
  27. package/src/CmsPage.tsx +139 -0
  28. package/src/Library.tsx +2 -0
  29. package/src/appFiles.ts +2 -2
  30. package/src/apps.ts +2 -2
  31. package/src/blocks.ts +1 -1
  32. package/src/cmsView.d.mts +15 -0
  33. package/src/cmsView.mjs +78 -0
  34. package/src/cmsView.test.d.mts +1 -0
  35. package/src/cmsView.test.mjs +146 -0
  36. package/src/frontmatter.mjs +4 -1
  37. package/src/frontmatter.test.mjs +6 -0
  38. package/src/i18n/da-DK/cms.ts +30 -0
  39. package/src/i18n/da-DK/common.ts +1 -0
  40. package/src/i18n/da-DK/index.ts +2 -0
  41. package/src/i18n/en-US/cms.ts +34 -0
  42. package/src/i18n/en-US/common.ts +2 -0
  43. package/src/i18n/en-US/index.ts +2 -0
  44. package/src/registry.tsx +3 -3
  45. package/src/schema.ts +1 -1
  46. package/starter/.prettierignore +1 -0
  47. package/starter/README.md +1 -0
  48. package/starter/package.json +1 -1
  49. package/starter/wrangler.jsonc +1 -1
  50. package/worker/cms-update.js +245 -0
  51. package/worker/github.js +8 -6
  52. package/worker/index.js +6 -1
@@ -0,0 +1,15 @@
1
+ export function cmsView(status: any, now?: number): {
2
+ running: any;
3
+ checkedAt: any;
4
+ latest: any;
5
+ notes: any[];
6
+ action: string | null;
7
+ npmCommand: string | null;
8
+ update: {
9
+ state: any;
10
+ to: any;
11
+ at: any;
12
+ } | null;
13
+ problems: any[];
14
+ };
15
+ export const DONE_SHOWN: number;
@@ -0,0 +1,78 @@
1
+ // Transforms CMS status into a view for the update page.
2
+ // Exposes cmsView and DONE_SHOWN.
3
+ // Depends on cms-version for version comparison.
4
+
5
+ import { compareVersions } from '../lib/cms-version.mjs';
6
+
7
+ export const DONE_SHOWN = 24 * 60 * 60 * 1000;
8
+
9
+ export function cmsView(status, now = Date.now()) {
10
+ const running = status.running;
11
+ const checkedAt = status.checked?.at ?? null;
12
+
13
+ let latest = null;
14
+ if (status.checked?.latest) {
15
+ try {
16
+ if (compareVersions(status.checked.latest, running) === 1) {
17
+ latest = status.checked.latest;
18
+ }
19
+ } catch {
20
+ // Pre-release or invalid version, ignore
21
+ }
22
+ }
23
+
24
+ let notes = [];
25
+ if (latest !== null && status.notes && status.notes.length > 0) {
26
+ notes = [...status.notes];
27
+ try {
28
+ notes.sort((a, b) => compareVersions(b.version, a.version));
29
+ } catch {
30
+ // Keep original order if sorting fails
31
+ }
32
+ }
33
+
34
+ const busy = status.update?.state === 'waiting' || status.update?.state === 'undoing';
35
+
36
+ let action = null;
37
+ if (latest !== null && !busy) {
38
+ if (status.checked?.problem?.kind === 'major') {
39
+ action = 'update';
40
+ } else if (status.checked?.problem?.kind === 'skipped') {
41
+ action = 'try-again';
42
+ }
43
+ }
44
+
45
+ let npmCommand = null;
46
+ if (latest !== null && status.checked?.problem?.kind === 'needs-npm') {
47
+ npmCommand = `npm install @morphemecms/cms@${latest}`;
48
+ }
49
+
50
+ let update = null;
51
+ if (status.update) {
52
+ const state = status.update.state;
53
+ if (state === 'waiting' || state === 'undoing' || state === 'undone' || state === 'stuck') {
54
+ update = { state, to: status.update.to, at: status.update.at };
55
+ } else if (state === 'done') {
56
+ const at = Date.parse(status.update.at);
57
+ if (now - at < DONE_SHOWN) {
58
+ update = { state, to: status.update.to, at: status.update.at };
59
+ }
60
+ }
61
+ }
62
+
63
+ const problems = [];
64
+ if (status.token === false) {
65
+ problems.push('no-token');
66
+ }
67
+ if (status.checked?.problem) {
68
+ const kind = status.checked.problem.kind;
69
+ if (kind === 'unreachable' || kind === 'cannot-write' || kind === 'not-managed') {
70
+ problems.push(kind);
71
+ }
72
+ }
73
+ if (status.checked?.noChecks === true) {
74
+ problems.push('slow-undo');
75
+ }
76
+
77
+ return { running, checkedAt, latest, notes, action, npmCommand, update, problems };
78
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,146 @@
1
+ // Tests for cmsView.
2
+ // Verifies status transformation and output shape.
3
+ // Run: node --test packages/editor/src/cmsView.test.mjs
4
+
5
+ import { test } from 'node:test';
6
+ import assert from 'node:assert/strict';
7
+ import { cmsView } from './cmsView.mjs';
8
+
9
+ const baseStatus = {
10
+ running: '0.1.1',
11
+ token: true,
12
+ kv: true,
13
+ checked: { at: '2026-11-04T10:00:00.000Z', latest: '0.1.1' },
14
+ update: null,
15
+ skipped: [],
16
+ notes: [],
17
+ };
18
+
19
+ const now = Date.parse('2026-11-04T12:00:00.000Z');
20
+
21
+ function makeStatus(overrides) {
22
+ const result = { ...baseStatus, ...overrides };
23
+
24
+ if (!('checked' in overrides)) {
25
+ result.checked = baseStatus.checked;
26
+ } else if (overrides.checked === null) {
27
+ result.checked = null;
28
+ } else {
29
+ result.checked = { ...baseStatus.checked, ...overrides.checked };
30
+ }
31
+
32
+ if (!('update' in overrides)) {
33
+ result.update = baseStatus.update;
34
+ } else if (overrides.update === null) {
35
+ result.update = null;
36
+ } else {
37
+ result.update = { ...baseStatus.update, ...overrides.update };
38
+ }
39
+
40
+ return result;
41
+ }
42
+
43
+ test('up to date', () => {
44
+ const view = cmsView(baseStatus, now);
45
+ assert.equal(view.latest, null);
46
+ assert.equal(view.action, null);
47
+ assert.equal(view.npmCommand, null);
48
+ assert.equal(view.update, null);
49
+ assert.deepEqual(view.problems, []);
50
+ assert.equal(view.checkedAt, '2026-11-04T10:00:00.000Z');
51
+ });
52
+
53
+ test('not checked', () => {
54
+ const status = makeStatus({ checked: null });
55
+ const view = cmsView(status, now);
56
+ assert.equal(view.checkedAt, null);
57
+ assert.equal(view.latest, null);
58
+ });
59
+
60
+ test('patch waiting', () => {
61
+ const status = makeStatus({
62
+ checked: { latest: '0.1.2' },
63
+ update: { commit: 'a1', from: '0.1.1', to: '0.1.2', at: '2026-11-04T11:00:00.000Z', state: 'waiting' },
64
+ });
65
+ const view = cmsView(status, now);
66
+ assert.equal(view.update.state, 'waiting');
67
+ assert.equal(view.update.to, '0.1.2');
68
+ assert.equal(view.action, null);
69
+ });
70
+
71
+ test('major', () => {
72
+ const status = makeStatus({ checked: { latest: '1.0.0', problem: { kind: 'major' } } });
73
+ const view = cmsView(status, now);
74
+ assert.equal(view.latest, '1.0.0');
75
+ assert.equal(view.action, 'update');
76
+ });
77
+
78
+ test('major while undoing', () => {
79
+ const status = makeStatus({
80
+ checked: { latest: '1.0.0', problem: { kind: 'major' } },
81
+ update: { commit: 'a1', from: '1.0.0', to: '0.1.1', at: '2026-11-04T11:00:00.000Z', state: 'undoing' },
82
+ });
83
+ const view = cmsView(status, now);
84
+ assert.equal(view.action, null);
85
+ });
86
+
87
+ test('skipped', () => {
88
+ const status = makeStatus({ checked: { latest: '0.1.3', problem: { kind: 'skipped' } } });
89
+ const view = cmsView(status, now);
90
+ assert.equal(view.action, 'try-again');
91
+ });
92
+
93
+ test('needs npm', () => {
94
+ const status = makeStatus({ checked: { latest: '0.1.2', problem: { kind: 'needs-npm', detail: 'x' } } });
95
+ const view = cmsView(status, now);
96
+ assert.equal(view.npmCommand, 'npm install @morphemecms/cms@0.1.2');
97
+ assert.equal(view.action, null);
98
+ });
99
+
100
+ test('no token', () => {
101
+ const status = makeStatus({ token: false });
102
+ const view = cmsView(status, now);
103
+ assert.deepEqual(view.problems, ['no-token']);
104
+ });
105
+
106
+ test('cannot write', () => {
107
+ const status = makeStatus({ checked: { problem: { kind: 'cannot-write' } } });
108
+ const view = cmsView(status, now);
109
+ assert.deepEqual(view.problems, ['cannot-write']);
110
+ });
111
+
112
+ test('slow undo', () => {
113
+ const status = makeStatus({ checked: { noChecks: true } });
114
+ const view = cmsView(status, now);
115
+ assert.ok(view.problems.includes('slow-undo'));
116
+ });
117
+
118
+ test('done two days ago', () => {
119
+ const status = makeStatus({ update: { commit: 'a1', from: '0.1.1', to: '0.1.2', at: '2026-11-02T12:00:00.000Z', state: 'done' } });
120
+ const view = cmsView(status, now);
121
+ assert.equal(view.update, null);
122
+ });
123
+
124
+ test('done an hour ago', () => {
125
+ const status = makeStatus({ update: { commit: 'a1', from: '0.1.1', to: '0.1.2', at: '2026-11-04T11:00:00.000Z', state: 'done' } });
126
+ const view = cmsView(status, now);
127
+ assert.equal(view.update.state, 'done');
128
+ });
129
+
130
+ test('notes order', () => {
131
+ const status = makeStatus({
132
+ checked: { latest: '0.1.3' },
133
+ notes: [
134
+ { version: '0.1.2', text: 'b' },
135
+ { version: '0.1.3', text: 'c' },
136
+ ],
137
+ });
138
+ const view = cmsView(status, now);
139
+ assert.equal(view.notes[0].version, '0.1.3');
140
+ });
141
+
142
+ test('pre-release running', () => {
143
+ const status = makeStatus({ running: '0.2.0-next.1', checked: { latest: '0.2.0' } });
144
+ const view = cmsView(status, now);
145
+ assert.equal(view.latest, null);
146
+ });
@@ -28,7 +28,10 @@ export function writeFields(yaml, changes) {
28
28
  if (value !== '' && value != null) doc.set(key, toNode(doc, key, value));
29
29
  else if (doc.has(key)) doc.delete(key);
30
30
  }
31
- return doc.contents?.items?.length ? doc.toString(STRINGIFY).replace(/\n$/, '') : '';
31
+ if (!doc.contents?.items?.length) return '';
32
+ const text = doc.toString(STRINGIFY).replace(/\n$/, '');
33
+ // The yaml package writes \n; a file with \r\n keeps its line breaks (a checkout on Windows).
34
+ return yaml.includes('\r\n') ? text.replace(/\n/g, '\r\n') : text;
32
35
  }
33
36
 
34
37
  function toNode(doc, key, value) {
@@ -25,3 +25,9 @@ test('a switch without a missing value is written as it is', () => {
25
25
  assert.equal(booleanWrite(true, undefined), true);
26
26
  assert.equal(booleanWrite(false, undefined), false);
27
27
  });
28
+
29
+ test('front matter with CRLF line breaks is written back unchanged, and added fields use CRLF too', () => {
30
+ const crlf = 'title: "Om os"\r\nwide: false';
31
+ assert.equal(writeFields(crlf, {}), crlf);
32
+ assert.equal(writeFields(crlf, { side: false }), `${crlf}\r\nside: false`);
33
+ });
@@ -0,0 +1,30 @@
1
+ import type { Messages } from '../en-US';
2
+
3
+ export const cms: Messages['cms'] = {
4
+ running: (v) => `Version ${v}`,
5
+ checked: (when) => `Tjekket ${when}`,
6
+ notChecked: 'Ikke tjekket endnu',
7
+ upToDate: 'Opdateret',
8
+ available: (v) => `Version ${v} er klar`,
9
+ whatsNew: 'Nyheder',
10
+ major: 'En ny hovedversion installeres ikke af sig selv.',
11
+ skipped: (v) => `${v} blev fortrudt, fordi siden ikke kunne bygges.`,
12
+ needsNpm: 'Den kræver npm. Kør dette i sitets repo:',
13
+ update: 'Opdater',
14
+ tryAgain: 'Prøv igen',
15
+ waiting: (v) => `Opdaterer til ${v}. Venter på, at siden bygges.`,
16
+ undoing: (v) => `Fortryder opdateringen til ${v}.`,
17
+ done: (v) => `Opdateret til ${v}`,
18
+ undone: (v) => `Opdateringen til ${v} blev fortrudt, fordi siden ikke kunne bygges.`,
19
+ stuck: (v) => `Hverken opdateringen til ${v} eller fortrydelsen kunne bygges. Siden skal rettes i hånden.`,
20
+ unreachable: 'npm eller GitHub kunne ikke nås.',
21
+ cannotWrite: 'Sitets GitHub-nøgle kan ikke gemme. Giv den Contents: Read and write.',
22
+ noToken: 'Sitet har ingen GitHub-nøgle (GITHUB_PUBLISH_TOKEN).',
23
+ notManaged: 'Sitets package.json bestemmer versionen.',
24
+ slowUndo: 'Nøglen kan ikke læse check runs, så en fejlet opdatering fortrydes først efter 20 minutter.',
25
+ notifyDone: (v) => `Morpheme er opdateret til ${v}`,
26
+ notifyUndone: (v) => `Opdateringen af Morpheme til ${v} er fortrudt, fordi siden ikke kunne bygges`,
27
+ notifyStuck: (v) => `Morpheme ${v} kunne ikke bygges, og det kunne fortrydelsen heller ikke. Ret siden i hånden.`,
28
+ commitUpdate: (v) => `Opdater Morpheme til ${v}`,
29
+ commitUndo: (v) => `Fortryd opdateringen af Morpheme til ${v}`,
30
+ };
@@ -110,6 +110,7 @@ export const common: Messages['common'] = {
110
110
  settings: 'Indstillinger',
111
111
  advanced: 'Avanceret',
112
112
  licenses: 'Licenser',
113
+ morpheme: 'Morpheme',
113
114
  },
114
115
  saved: (change) => `${change}. Du får besked, når hjemmesiden er opdateret.`,
115
116
  today: 'i dag',
@@ -6,6 +6,7 @@ import { apps } from './apps';
6
6
  import { blocks } from './blocks';
7
7
  import { blockSettings } from './blockSettings';
8
8
  import { check } from './check';
9
+ import { cms } from './cms';
9
10
  import { colors } from './colors';
10
11
  import { common } from './common';
11
12
  import { company } from './company';
@@ -41,6 +42,7 @@ export const daDK: Messages = {
41
42
  seo,
42
43
  keywords,
43
44
  licenses,
45
+ cms,
44
46
  parts,
45
47
  history,
46
48
  preview,
@@ -0,0 +1,34 @@
1
+ // "Morpheme" under "Indstillinger" (the CMS's update page, #morpheme): the running version, a new one and its notes,
2
+ // and an update's progress; also the Worker's notifications and commit messages (worker/cms-update.js).
3
+
4
+ export const cms = {
5
+ running: (v: string) => `Version ${v}`,
6
+ /** When the Worker last asked npm, as when.ts writes it. */
7
+ checked: (when: string) => `Checked ${when}`,
8
+ notChecked: 'Not checked yet',
9
+ upToDate: 'Up to date',
10
+ available: (v: string) => `Version ${v} is available`,
11
+ whatsNew: 'What’s new',
12
+ major: 'A new major version isn’t installed by itself.',
13
+ skipped: (v: string) => `${v} was undone because the site couldn’t be built.`,
14
+ /** Followed by the command, in a code block. */
15
+ needsNpm: 'It needs npm. Run this in the site’s repository:',
16
+ update: 'Update',
17
+ tryAgain: 'Try again',
18
+ waiting: (v: string) => `Updating to ${v}. Waiting for the site to be built.`,
19
+ undoing: (v: string) => `Undoing the update to ${v}.`,
20
+ done: (v: string) => `Updated to ${v}`,
21
+ undone: (v: string) => `The update to ${v} was undone because the site couldn’t be built.`,
22
+ stuck: (v: string) => `The update to ${v} and its undo couldn’t be built. The site must be fixed by hand.`,
23
+ unreachable: 'npm or GitHub couldn’t be reached.',
24
+ cannotWrite: 'The site’s GitHub token can’t save. Give it Contents: Read and write.',
25
+ noToken: 'The site has no GitHub token (GITHUB_PUBLISH_TOKEN).',
26
+ notManaged: 'The site’s package.json decides the version.',
27
+ slowUndo: 'The token can’t read check runs, so a failed update is undone after 20 minutes.',
28
+ /** The site's notifications (Notifications.tsx) and the commits the Worker writes, in the site's language. */
29
+ notifyDone: (v: string) => `Morpheme was updated to ${v}`,
30
+ notifyUndone: (v: string) => `The update of Morpheme to ${v} was undone because the site couldn’t be built`,
31
+ notifyStuck: (v: string) => `Morpheme ${v} couldn’t be built, and neither could its undo. Fix the site by hand.`,
32
+ commitUpdate: (v: string) => `Update Morpheme to ${v}`,
33
+ commitUndo: (v: string) => `Undo the update of Morpheme to ${v}`,
34
+ };
@@ -134,6 +134,8 @@ export const common = {
134
134
  advanced: 'Advanced',
135
135
  /** Under Indstillinger: the licences of what the site and the editor are made with (#licenser, Licenses.tsx). */
136
136
  licenses: 'Licenses',
137
+ /** Under Indstillinger: the CMS's version and its updates (#morpheme). */
138
+ morpheme: 'Morpheme',
137
139
  },
138
140
  /** Right after a save, with what it changed (savedText in deploy.ts); the site is rebuilt in 1-2 minutes. */
139
141
  saved: (change: string) => `${change}. You’ll get a message when the website is updated.`,
@@ -6,6 +6,7 @@ import { apps } from './apps';
6
6
  import { blocks } from './blocks';
7
7
  import { blockSettings } from './blockSettings';
8
8
  import { check } from './check';
9
+ import { cms } from './cms';
9
10
  import { colors } from './colors';
10
11
  import { common } from './common';
11
12
  import { company } from './company';
@@ -41,6 +42,7 @@ export const enUS = {
41
42
  seo,
42
43
  keywords,
43
44
  licenses,
45
+ cms,
44
46
  parts,
45
47
  history,
46
48
  preview,
package/src/registry.tsx CHANGED
@@ -9,8 +9,8 @@
9
9
  // and its look is the theme's (packages/editor/theme/blocks/<type>/, or the theme in use's): View.tsx, which draws its
10
10
  // view in the editor (blockViews, from standard/themeBlocks.ts) and on the site. A new folder is a new block, in the editor and on
11
11
  // the site; nothing else lists them. The apps (src/apps/<app>/, apps.ts) add blocks the same way, in their folder's
12
- // blocks/, each with its own View.tsx; they are offered once the app is installed. A theme has no blocks of its own,
13
- // only the look of the standard ones.
12
+ // blocks/, each with its own View.tsx and viewOf.ts for view.ts (it would differ from View.tsx only in case); they
13
+ // are offered once the app is installed. A theme has no blocks of its own, only the look of the standard ones.
14
14
  import type { ReactNode } from 'react';
15
15
  import { appIdOf, installedApps } from './apps';
16
16
  import { htmlBlock, type BlockData, type BlockDefinition, type BlockProps, type DrawBlock, type SiteBlock } from './blocks';
@@ -60,7 +60,7 @@ export const siteBlocks: SiteBlock[] = [
60
60
  ...Object.entries(fromApps).map(([path, m]) =>
61
61
  localSiteBlock({
62
62
  ...m.default,
63
- draw: drawn(appViews[beside(path, 'View.tsx')]?.default, appViewOfs[beside(path, 'view.ts')]?.viewOf),
63
+ draw: drawn(appViews[beside(path, 'View.tsx')]?.default, appViewOfs[beside(path, 'viewOf.ts')]?.viewOf),
64
64
  type: typeOf(path),
65
65
  app: appIdOf(path),
66
66
  }),
package/src/schema.ts CHANGED
@@ -264,7 +264,7 @@ export const entryName = (collection: FolderCollection, data: Record<string, unk
264
264
 
265
265
  /** An app's part of an entry of its kind, drawn in the list's own drawing (a Mailchimp signup in a pop-up's box), on the
266
266
  * site and in the editor alike: its drawing (`View`, given its view and the entry's own button to show where it wants)
267
- * and what makes its view in the editor (`viewOf`, as a block's view.ts). */
267
+ * and what makes its view in the editor (`viewOf`, in its viewOf.ts, as an app's block). */
268
268
  export type KindPart = {
269
269
  View: ComponentType<{ view: any; button: ReactNode }>;
270
270
  viewOf: (values: Record<string, any>, data: BlockData) => unknown;
@@ -8,4 +8,5 @@ package-lock.json
8
8
  src/content
9
9
  src/data/**/*.json
10
10
  src/apps/*/store.json
11
+ src/themes
11
12
  *.md
package/starter/README.md CHANGED
@@ -9,6 +9,7 @@ Editoren, standardtemaet *Material* og sitets Worker er pakken [`@morphemecms/cm
9
9
 
10
10
  - `src/site/config.ts`: sitets navn, domæne, sprog og GitHub-repo; resten er pakkens standard (`defineSite`).
11
11
  - `src/site/worker.js`: det, Workeren har fra sitet.
12
+ - Hemmeligheden `GITHUB_PUBLISH_TOKEN` (`npx wrangler secret put GITHUB_PUBLISH_TOKEN`) lader Workeren skrive til sitets GitHub-repo, også når den opdaterer CMS'et. Tokenet skal have **Contents: Read and write** og **Checks: Read**.
12
13
  - Hemmeligheden `MORPHEME_STORE_KEY` (`npx wrangler secret put MORPHEME_STORE_KEY`) forbinder sitet til Morpheme-butikken; uden den har *Appstore* kun sitets egne apps og temaer.
13
14
  - `src/content/`: siderne og indlæggene, `src/data/`: sitets indstillinger, `public/`: billeder, logo og ikoner.
14
15
  - `src/themes/` og `src/apps/`: temaer og apps fra appbutikken, når der er installeret nogen.
@@ -31,7 +31,7 @@
31
31
  "@fontsource-variable/roboto": "^5.3.0",
32
32
  "@material/material-color-utilities": "^0.3.0",
33
33
  "@milkdown/kit": "^7.22.3",
34
- "@morphemecms/cms": "^0.1.0",
34
+ "@morphemecms/cms": "0.3.0",
35
35
  "@phosphor-icons/core": "^2.1.1",
36
36
  "@phosphor-icons/react": "^2.1.10",
37
37
  "@tailwindcss/typography": "^0.5.20",
@@ -5,7 +5,7 @@
5
5
  "$schema": "node_modules/wrangler/config-schema.json",
6
6
  "name": "morpheme-site",
7
7
  "compatibility_date": "2026-09-01",
8
- "compatibility_flags": ["nodejs_compat"],
8
+ "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"], // public fetch: the store is on this account
9
9
  "main": "node_modules/@morphemecms/cms/worker/index.js",
10
10
  // What the Worker has from this site: its settings and src/site/worker.js (its redirects, apps and contact).
11
11
  "alias": { "@site/config": "./src/site/config.ts", "@site/worker": "./src/site/worker.js" },