apostrophe 4.30.0 → 4.31.0-alpha.1
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 +67 -2
- package/claude-tools/detect-handles.js +46 -0
- package/claude-tools/minimal-hang-test.js +28 -0
- package/claude-tools/mongo-close-test.js +11 -0
- package/claude-tools/stdin-ref-test.js +14 -0
- package/eslint.config.js +3 -1
- package/modules/@apostrophecms/area/index.js +94 -2
- package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -40
- package/modules/@apostrophecms/area/ui/apos/components/AposBreadcrumbOperations.vue +0 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +0 -1
- package/modules/@apostrophecms/attachment/index.js +4 -1
- package/modules/@apostrophecms/db/index.js +68 -27
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +5 -3
- package/modules/@apostrophecms/express/index.js +2 -0
- package/modules/@apostrophecms/http/index.js +1 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +3 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +2 -2
- package/modules/@apostrophecms/job/index.js +9 -7
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridColumn.vue +0 -1
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridManager.vue +0 -1
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +10 -2
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLoginHeader.vue +3 -3
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +52 -23
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +6 -1
- package/modules/@apostrophecms/oembed/index.js +2 -1
- package/modules/@apostrophecms/piece-page-type/index.js +7 -0
- package/modules/@apostrophecms/piece-type/index.js +2 -1
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +7 -2
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposCellTitle.vue +1 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +21 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputDateAndTime.vue +7 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSubform.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSubform.js +10 -0
- package/modules/@apostrophecms/styles/ui/apos/components/TheAposStyles.vue +1 -0
- package/modules/@apostrophecms/template/index.js +117 -11
- package/modules/@apostrophecms/template/lib/jsxLoader.js +128 -0
- package/modules/@apostrophecms/template/lib/jsxRender.js +490 -0
- package/modules/@apostrophecms/template/lib/jsxRuntime.js +276 -0
- package/modules/@apostrophecms/template/lib/nunjucksLoader.js +11 -36
- package/modules/@apostrophecms/template/lib/viewWatcher.js +113 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonGroup.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellLastEdited.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposSelect.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +10 -4
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +6 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposSubformPreview.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
- package/modules/@apostrophecms/uploadfs/index.js +3 -0
- package/modules/@apostrophecms/util/index.js +3 -3
- package/package.json +14 -10
- package/test/add-missing-schema-fields-project/test.js +22 -3
- package/test/assets.js +110 -67
- package/test/db-tools.js +365 -0
- package/test/db.js +24 -15
- package/test/default-adapter.js +256 -0
- package/test/external-front.js +419 -1
- package/test/job.js +1 -1
- package/test/modules/jsx-area-test/index.js +23 -0
- package/test/modules/jsx-area-test/views/bad-area.jsx +7 -0
- package/test/modules/jsx-area-test/views/with-area-ctx.jsx +13 -0
- package/test/modules/jsx-area-test/views/with-area.jsx +7 -0
- package/test/modules/jsx-area-test/views/with-widget-ctx.jsx +12 -0
- package/test/modules/jsx-area-test/views/with-widget.jsx +7 -0
- package/test/modules/jsx-async-widget/index.js +6 -0
- package/test/modules/jsx-async-widget/views/widget.jsx +11 -0
- package/test/modules/jsx-bridge-test/index.js +1 -0
- package/test/modules/jsx-bridge-test/views/cross-module.jsx +7 -0
- package/test/modules/jsx-bridge-test/views/disambig-name-only.jsx +7 -0
- package/test/modules/jsx-bridge-test/views/disambig-target.jsx +8 -0
- package/test/modules/jsx-bridge-test/views/disambig-with-template-name.jsx +7 -0
- package/test/modules/jsx-bridge-test/views/include-html.jsx +7 -0
- package/test/modules/jsx-bridge-test/views/include-target.html +4 -0
- package/test/modules/jsx-bridge-test/views/jsx-extends-via-extend.jsx +9 -0
- package/test/modules/jsx-bridge-test/views/jsx-extends.jsx +9 -0
- package/test/modules/jsx-bridge-test/views/jsx-layout.jsx +14 -0
- package/test/modules/jsx-bridge-test/views/njk-extends.jsx +14 -0
- package/test/modules/jsx-bridge-test/views/njk-layout.html +9 -0
- package/test/modules/jsx-bridge-test/views/short-form.jsx +7 -0
- package/test/modules/jsx-bridge-test/views/short-target.jsx +3 -0
- package/test/modules/jsx-component-test/index.js +15 -0
- package/test/modules/jsx-component-test/views/greet.html +1 -0
- package/test/modules/jsx-component-test/views/uses-component.jsx +8 -0
- package/test/modules/jsx-ctx-widget/index.js +6 -0
- package/test/modules/jsx-ctx-widget/views/widget.jsx +4 -0
- package/test/modules/jsx-mixed-test/index.js +9 -0
- package/test/modules/jsx-mixed-test/views/apos-full.jsx +21 -0
- package/test/modules/jsx-mixed-test/views/async-list.jsx +12 -0
- package/test/modules/jsx-mixed-test/views/lib/format.js +3 -0
- package/test/modules/jsx-mixed-test/views/localized.jsx +3 -0
- package/test/modules/jsx-mixed-test/views/partial.jsx +3 -0
- package/test/modules/jsx-mixed-test/views/safe-helper.jsx +3 -0
- package/test/modules/jsx-mixed-test/views/syntax-error.jsx +3 -0
- package/test/modules/jsx-mixed-test/views/throws.jsx +5 -0
- package/test/modules/jsx-mixed-test/views/uses-import.jsx +5 -0
- package/test/modules/jsx-mixed-test/views/uses-require.jsx +5 -0
- package/test/modules/jsx-watcher-cross-test/index.js +5 -0
- package/test/modules/jsx-watcher-cross-test/views/cross-template.jsx +3 -0
- package/test/modules/jsx-watcher-test/index.js +5 -0
- package/test/modules/jsx-watcher-test/views/watcher-test.jsx +3 -0
- package/test/modules/template-jsx-options-test/index.js +12 -0
- package/test/modules/template-jsx-options-test/views/options-test.jsx +9 -0
- package/test/modules/template-jsx-subclass-test/index.js +3 -0
- package/test/modules/template-jsx-subclass-test/views/override-test.jsx +3 -0
- package/test/modules/template-jsx-test/index.js +9 -0
- package/test/modules/template-jsx-test/views/boolean-attrs.jsx +11 -0
- package/test/modules/template-jsx-test/views/class-and-for.jsx +7 -0
- package/test/modules/template-jsx-test/views/dangerously-set.jsx +3 -0
- package/test/modules/template-jsx-test/views/escape-attr.jsx +3 -0
- package/test/modules/template-jsx-test/views/escape-body.jsx +3 -0
- package/test/modules/template-jsx-test/views/inherit-test.jsx +3 -0
- package/test/modules/template-jsx-test/views/list.jsx +7 -0
- package/test/modules/template-jsx-test/views/override-test.jsx +3 -0
- package/test/modules/template-jsx-test/views/svg-attrs.jsx +27 -0
- package/test/modules/template-jsx-test/views/test.jsx +3 -0
- package/test/modules/template-jsx-test/views/void-elements.jsx +9 -0
- package/test/templates-jsx-watcher.js +135 -0
- package/test/templates-jsx.js +537 -0
- package/test-lib/util.js +50 -14
- package/.claude/settings.local.json +0 -15
- package/lib/mongodb-connect.js +0 -62
- package/test/add-missing-schema-fields-project/node_modules/.package-lock.json +0 -131
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<span class="greet">Hello {{ data.who }}{% if data.afterDelay %} (after delay){% endif %}</span>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default async function (data, { apos, helpers }) {
|
|
2
|
+
const id = apos.util.generateId();
|
|
3
|
+
// apos.doc.find returns a real cursor we can await. apos.doc here is the
|
|
4
|
+
// doc-module instance from self.apos, not a helper bag.
|
|
5
|
+
const docs = await apos.doc.find(data.req, { type: '@apostrophecms/global' }).toArray();
|
|
6
|
+
// The doc module instance has methods (e.g. find) that the helper bag
|
|
7
|
+
// does not — and the two `modules` collections are different objects.
|
|
8
|
+
const aposDocIsModule = typeof apos.doc.find === 'function';
|
|
9
|
+
const helpersDocIsHelperBag = (apos.doc !== helpers.modules['@apostrophecms/doc']);
|
|
10
|
+
const aposModulesIsNotHelpersModules = (apos.modules !== helpers.modules);
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
data-id-length={String(id.length)}
|
|
14
|
+
data-global-count={String(docs.length)}
|
|
15
|
+
data-distinct={String(apos !== helpers)}
|
|
16
|
+
data-apos-doc-is-module={String(aposDocIsModule)}
|
|
17
|
+
data-helpers-doc-is-helper-bag={String(helpersDocIsHelperBag)}
|
|
18
|
+
data-modules-distinct={String(aposModulesIsNotHelpersModules)}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Verifies that camelCase SVG presentation attributes are emitted in
|
|
2
|
+
// the kebab-case form expected by browsers parsing text/html, and that
|
|
3
|
+
// natively camelCase SVG attributes (viewBox, preserveAspectRatio) are
|
|
4
|
+
// preserved.
|
|
5
|
+
|
|
6
|
+
export default function () {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
xmlns='http://www.w3.org/2000/svg'
|
|
10
|
+
viewBox='0 0 24 24'
|
|
11
|
+
preserveAspectRatio='xMidYMid meet'
|
|
12
|
+
fill='none'
|
|
13
|
+
stroke='currentColor'
|
|
14
|
+
strokeWidth='2'
|
|
15
|
+
strokeLinecap='round'
|
|
16
|
+
strokeLinejoin='round'
|
|
17
|
+
strokeDasharray='4 2'
|
|
18
|
+
strokeOpacity='0.5'
|
|
19
|
+
fillRule='evenodd'
|
|
20
|
+
clipRule='evenodd'
|
|
21
|
+
pointerEvents='none'
|
|
22
|
+
textAnchor='middle'
|
|
23
|
+
>
|
|
24
|
+
<path d='M0 0L24 24' />
|
|
25
|
+
</svg>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert/strict');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const isWsl = require('is-wsl');
|
|
6
|
+
|
|
7
|
+
describe('Templates: JSX watcher', function () {
|
|
8
|
+
|
|
9
|
+
let apos;
|
|
10
|
+
this.timeout(t.timeout);
|
|
11
|
+
|
|
12
|
+
// Two fixture modules. `jsx-watcher-test` is the simple case: render its
|
|
13
|
+
// own template directly. `jsx-watcher-cross-test` is the realistic-page
|
|
14
|
+
// case: another module renders this one's template via the qualified
|
|
15
|
+
// `module:template` form (mirroring how `@apostrophecms/page` renders
|
|
16
|
+
// `@apostrophecms/home-page:page` in the wild). The first surfaces a
|
|
17
|
+
// bug where renderBody never arms a watcher for JSX renders; the second
|
|
18
|
+
// surfaces a bug where the watcher was armed against the caller's views
|
|
19
|
+
// instead of the resolved module's views.
|
|
20
|
+
const ownModule = 'jsx-watcher-test';
|
|
21
|
+
const ownTemplate = 'watcher-test';
|
|
22
|
+
const ownPath = path.join(
|
|
23
|
+
__dirname, 'modules', ownModule, 'views', `${ownTemplate}.jsx`
|
|
24
|
+
);
|
|
25
|
+
const ownOriginal = 'export default function () {\n return <h1>own-original</h1>;\n}\n';
|
|
26
|
+
const ownChanged = 'export default function () {\n return <h1>own-changed</h1>;\n}\n';
|
|
27
|
+
|
|
28
|
+
const crossModule = 'jsx-watcher-cross-test';
|
|
29
|
+
const crossTemplate = 'cross-template';
|
|
30
|
+
const crossPath = path.join(
|
|
31
|
+
__dirname, 'modules', crossModule, 'views', `${crossTemplate}.jsx`
|
|
32
|
+
);
|
|
33
|
+
const crossOriginal = 'export default function () {\n return <h1>cross-original</h1>;\n}\n';
|
|
34
|
+
const crossChanged = 'export default function () {\n return <h1>cross-changed</h1>;\n}\n';
|
|
35
|
+
|
|
36
|
+
before(async function () {
|
|
37
|
+
// Defend against poisoned fixtures from a prior failed run.
|
|
38
|
+
fs.writeFileSync(ownPath, ownOriginal);
|
|
39
|
+
fs.writeFileSync(crossPath, crossOriginal);
|
|
40
|
+
apos = await t.create({
|
|
41
|
+
root: module,
|
|
42
|
+
modules: {
|
|
43
|
+
[ownModule]: {},
|
|
44
|
+
[crossModule]: {}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
after(async function () {
|
|
50
|
+
try {
|
|
51
|
+
fs.writeFileSync(ownPath, ownOriginal);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// best-effort restore
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
fs.writeFileSync(crossPath, crossOriginal);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
// best-effort restore
|
|
59
|
+
}
|
|
60
|
+
return t.destroy(apos);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Wait for chokidar's initial scan to complete on every watcher the
|
|
64
|
+
// template module has registered. If the JSX render path failed to arm
|
|
65
|
+
// any watcher for our views directory, no `change` event would ever
|
|
66
|
+
// fire and the caller's `waitForChange` would time out.
|
|
67
|
+
async function waitForWatchersReady() {
|
|
68
|
+
await Promise.all(
|
|
69
|
+
(apos.template._viewWatchers || []).map(watcher => {
|
|
70
|
+
if (watcher._readyEmitted) {
|
|
71
|
+
return Promise.resolve();
|
|
72
|
+
}
|
|
73
|
+
return new Promise(resolve => watcher.on('ready', resolve));
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function waitForChange(absolutePath) {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const timer = setTimeout(
|
|
81
|
+
() => reject(new Error(`No view-change event was observed for ${absolutePath}.`)),
|
|
82
|
+
5000
|
|
83
|
+
);
|
|
84
|
+
apos.template.onViewChange(filePath => {
|
|
85
|
+
if (filePath && path.resolve(filePath) === path.resolve(absolutePath)) {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
resolve();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
it('should pick up edits to a .jsx template rendered through its own module', async function () {
|
|
94
|
+
if (isWsl) {
|
|
95
|
+
this.skip();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const req = apos.task.getAnonReq();
|
|
99
|
+
const initial = await apos.modules[ownModule].render(req, ownTemplate);
|
|
100
|
+
assert.match(initial, /own-original/);
|
|
101
|
+
|
|
102
|
+
await waitForWatchersReady();
|
|
103
|
+
const sawChange = waitForChange(ownPath);
|
|
104
|
+
fs.writeFileSync(ownPath, ownChanged);
|
|
105
|
+
await sawChange;
|
|
106
|
+
|
|
107
|
+
const after = await apos.modules[ownModule].render(req, ownTemplate);
|
|
108
|
+
assert.match(after, /own-changed/);
|
|
109
|
+
assert.doesNotMatch(after, /own-original/);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should pick up edits when one module renders another module\'s .jsx via `module:template`', async function () {
|
|
113
|
+
if (isWsl) {
|
|
114
|
+
this.skip();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const req = apos.task.getAnonReq();
|
|
118
|
+
// The caller is `ownModule`, but the template lives in `crossModule`.
|
|
119
|
+
// This is the shape that breaks if the watcher is armed against the
|
|
120
|
+
// caller's view chain instead of the resolved file's module.
|
|
121
|
+
const qualified = `${crossModule}:${crossTemplate}`;
|
|
122
|
+
const initial = await apos.modules[ownModule].render(req, qualified);
|
|
123
|
+
assert.match(initial, /cross-original/);
|
|
124
|
+
|
|
125
|
+
await waitForWatchersReady();
|
|
126
|
+
const sawChange = waitForChange(crossPath);
|
|
127
|
+
fs.writeFileSync(crossPath, crossChanged);
|
|
128
|
+
await sawChange;
|
|
129
|
+
|
|
130
|
+
const after = await apos.modules[ownModule].render(req, qualified);
|
|
131
|
+
assert.match(after, /cross-changed/);
|
|
132
|
+
assert.doesNotMatch(after, /cross-original/);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
});
|