apostrophe 4.28.0 → 4.28.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.
@@ -0,0 +1,15 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(timeout 180 npx mocha:*)",
5
+ "Bash(timeout 600 npx mocha:*)",
6
+ "Bash(npm ls:*)",
7
+ "Bash(timeout 540 npx mocha:*)",
8
+ "Bash(echo:*)",
9
+ "Bash(timeout 10 node:*)",
10
+ "Bash(timeout 300 npx mocha:*)",
11
+ "Bash(timeout 60 npx mocha:*)",
12
+ "Bash(timeout 120 npx mocha:*)"
13
+ ]
14
+ }
15
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.28.1
4
+
5
+ ### Patch Changes
6
+
7
+ - f8d1952: Bug fix: the "pretty URLs" feature of @apostrophecms/file is now compatible with locale prefixes.
8
+
3
9
  ## 4.28.0
4
10
 
5
11
  ### Adds
@@ -8,7 +14,6 @@
8
14
  - Adds widget graph store, accessible in Admin UI.
9
15
  - Support for the new `prettyUrls: true` option for @apostrophecms/file, which enables "pretty URLs" for PDFs and other items in the file library, in exchange for a small performance impact. Edit the slug field to adjust the pretty URL
10
16
 
11
-
12
17
  ### Fixes
13
18
 
14
19
  - Fix a bug when rich text link open in new tab checkbox can't be cleared
@@ -27,7 +32,7 @@
27
32
  - Improve re-rendering UX while keeping the performance optimization
28
33
  - raise the user's widget z-index context only when focused
29
34
  - Hide add content buttons on rich text editing, like widget controls
30
- - Refine in-context focus states for calmer UX
35
+ - Refine in-context focus states for calmer UX
31
36
  - Simplifies some in-context UI rendering checks
32
37
  - Updated dependencies
33
38
 
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ <div align="center">
2
+ <a href="https://github.com/apostrophecms/apostrophe">
3
+ <img src="logo.svg" alt="ApostropheCMS logo" width="80" height="80">
4
+ </a>
5
+
6
+ <h1>ApostropheCMS</h1>
7
+
8
+ <p>
9
+ <a aria-label="Join the community on Discord" href="http://chat.apostrophecms.org">
10
+ <img alt="" src="https://img.shields.io/discord/517772094482677790?color=5865f2&label=Join%20the%20Discord&logo=discord&logoColor=fff&labelColor=000&style=for-the-badge&logoWidth=20" />
11
+ </a>
12
+ <a aria-label="License" href="https://github.com/apostrophecms/apostrophe/blob/main/LICENSE.md">
13
+ <img alt="" src="https://img.shields.io/static/v1?style=for-the-badge&labelColor=000000&label=License&message=MIT&color=3DA639" />
14
+ </a>
15
+ </p>
16
+
17
+ <p>
18
+ <strong>Full-stack CMS for developers and content teams</strong><br />
19
+ Build websites with in-context editing and headless flexibility using Node.js and MongoDB.
20
+ <br />
21
+ <a href="https://docs.apostrophecms.org/"><strong>Documentation »</strong></a>
22
+ <br />
23
+ <br />
24
+ <a href="http://demo.apostrophecms.com">Demo</a>
25
+ ·
26
+ <a href="https://roadmap.apostrophecms.com/roadmap">Roadmap</a>
27
+ ·
28
+ <a href="https://github.com/apostrophecms/apostrophe/issues/new?assignees=&labels=bug,3.0&template=bug_report.md&title=">Report Bug</a>
29
+ </p>
30
+ </div>
31
+
32
+ ## About
33
+
34
+ ApostropheCMS is a full-stack content management system built with Node.js and MongoDB. Content creators can edit directly on live pages without switching between admin interfaces, while developers can build with modern JavaScript or use it headlessly with any frontend framework.
35
+
36
+ ### Key Features
37
+
38
+ - **🎯 In-Context Editing** - Content creators edit directly on the live page, seeing changes instantly
39
+ - **⚡ Headless-Ready** - Use any frontend framework while keeping the powerful admin experience
40
+ - **🛠️ Developer-First** - Built with Node.js and MongoDB for full-stack JavaScript development
41
+ - **📈 Scales Beautifully** - From small sites to enterprise applications handling millions of pages
42
+ - **🔐 Enterprise Features** - Advanced permissions, workflow management, automated translations, and more
43
+
44
+ ## System Requirements
45
+
46
+ | Requirement | Version | Installation Notes |
47
+ |-------------|---------|-------------------|
48
+ | **Node.js** | 20.x+ | Use [NVM](https://github.com/nvm-sh/nvm) for version management |
49
+ | **MongoDB** | 6.0+ | [MongoDB Atlas](https://www.mongodb.com/atlas) (cloud) or local install |
50
+ | **npm** | 10.x+ | Included with Node.js |
51
+
52
+ See our [setup guides](https://docs.apostrophecms.org/guide/development-setup.html) for installation instructions.
53
+
54
+ ## Quick Start
55
+
56
+ Get ApostropheCMS running locally in minutes:
57
+
58
+ ```bash
59
+ # Option 1: Install CLI globally (recommended for multiple projects)
60
+ npm install -g @apostrophecms/cli
61
+ apos create my-website
62
+ cd my-website
63
+ npm run dev
64
+
65
+ # Option 2: Use npx for one-time project creation
66
+ npx @apostrophecms/cli create my-website
67
+ cd my-website
68
+ npm run dev
69
+ ```
70
+
71
+ Your new ApostropheCMS site will be available at `http://localhost:3000` with a powerful admin interface at `/login`.
72
+
73
+ ### Prefer to Go Headless?
74
+
75
+ **Get started with Astro integration** - the easiest way to build headless sites while keeping visual editing:
76
+
77
+ - **[Apollo Starter Kit (Astro)](https://apostrophecms.com/starter-kits/apollo-starter-kit-for-astro-cms)** - Production-ready foundation with beautiful design system and rich content features
78
+ - **[Essentials Starter Kit (Astro)](git clone https://github.com/apostrophecms/starter-kit-astro-essentials)** - Minimal, clean foundation for building custom designs from scratch
79
+
80
+ Both starter kits provide headless CMS power with in-context editing, letting content creators edit directly on the live site while you build with modern frontend tools. Our Astro integration handles all the content fetching automatically—no REST API calls to write.
81
+
82
+ **Desire a different frontend framework?** Use our REST APIs with React, Vue, Svelte, or any other framework:
83
+
84
+ - **[REST API Documentation](https://docs.apostrophecms.org/reference/api/pieces.html)** - Complete API reference
85
+ - **[Headless CMS Guide](https://docs.apostrophecms.org/guide/headless-cms.html)** - Integration walkthrough for any framework
86
+
87
+ ### Hosting & Deployment
88
+
89
+ Choose [ApostropheCMS hosting](https://apostrophecms.com/hosting) for turnkey solutions with optimized performance and dedicated support, or deploy to [any platform where Node.js runs](https://docs.apostrophecms.org/guide/hosting.html).
90
+
91
+ ## Built With Modern Tech
92
+
93
+ - **[Node.js](https://nodejs.org/)** - JavaScript runtime for server-side development
94
+ - **[MongoDB](https://www.mongodb.com/)** - Flexible document database for content storage
95
+ - **ESM Modules** - Native ES6 module support for modern JavaScript
96
+ - **Vite** - Lightning-fast build tool and development server
97
+ - **Modern JavaScript** - ES6+, async/await, and contemporary development patterns
98
+
99
+ ## Community & Support
100
+
101
+ **Join other developers and content creators using ApostropheCMS:**
102
+
103
+ - **[Discord](https://discord.com/invite/XkbRNq7)** - Get help, share projects, and connect with other users
104
+ - **[GitHub Discussions](https://github.com/apostrophecms/apostrophe/discussions)** - Feature requests, technical discussions, and community support
105
+ - **[Documentation](https://docs.apostrophecms.org/)** - Comprehensive guides, tutorials, and API references
106
+
107
+ ## Contributing
108
+
109
+ We welcome contributions from the community! Whether you're fixing bugs, adding features, or improving documentation, your help makes ApostropheCMS better for everyone.
110
+
111
+ - **[Contribution Guide](https://github.com/apostrophecms/apostrophe/blob/main/CONTRIBUTING.md)** - How to contribute code, documentation, and feedback
112
+ - **[Good First Issues](https://github.com/apostrophecms/apostrophe/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)** - Perfect starting points for new contributors
113
+
114
+
115
+ ## Pro Features
116
+
117
+ **For teams and organizations requiring additional features:**
118
+
119
+ - **🔐 Advanced User Management** - Granular permissions, user groups, and access controls
120
+ - **🌍 Automated Translation** - AI-powered translation with DeepL, Google Translate, and Azure
121
+ - **📊 Analytics & SEO** - Built-in SEO optimization and content analytics
122
+ - **⚡ Performance Optimization** - Advanced caching, CDN integration, and performance monitoring
123
+ - **🏢 Multisite Management** - Manage multiple sites from a single dashboard with shared resources
124
+ - **💼 Professional Support** - Dedicated support, training, and consultation services
125
+
126
+ [Explore all the pro extensions](https://apostrophecms.com/extensions?autocomplete=&license=assembly&license=pro) and [sign up](https://app.apostrophecms.com/login) for a Pro license in our self-service Apostrophe Workspaces, or [contact us](https://apostrophecms.com/contact-us) to learn about licensing and support options.
127
+
128
+ ## License
129
+
130
+ ApostropheCMS is open source software licensed under the [MIT License](https://github.com/apostrophecms/apostrophe/blob/main/LICENSE.md). This means you're free to use, modify, and distribute it for both personal and commercial projects.
131
+
132
+ ---
133
+
134
+ <div align="center">
135
+ <p>
136
+ <strong>Ready to build something amazing?</strong><br>
137
+ <a href="https://docs.apostrophecms.org/">Get started with our documentation</a> or <a href="https://apostrophecms.com/contact-us">talk to our team</a>
138
+ </p>
139
+ <p>
140
+ <em>Built with ❤️ by the <a href="https://apostrophecms.com">ApostropheCMS team</a></em>
141
+ </p>
142
+ </div>
@@ -111,8 +111,7 @@ module.exports = {
111
111
  // (the slug-taken route does that)
112
112
  if (self.options.prettyUrls && file.attachment) {
113
113
  const { extension } = file.attachment;
114
- const baseUrl = self.apos.url.getBaseUrl(req, { prefix: true });
115
- file._url = `${baseUrl}${self.options.prettyUrlDir}/${file.slug.replace(self.options.slugPrefix || '', '')}.${extension}`;
114
+ file._url = `${req.prefix}${self.options.prettyUrlDir}/${file.slug.replace(self.options.slugPrefix || '', '')}.${extension}`;
116
115
  file.attachment._prettyUrl = file._url;
117
116
  } else {
118
117
  file._url = self.apos.attachment.url(file.attachment);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "4.28.0",
3
+ "version": "4.28.1",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -119,12 +119,12 @@
119
119
  "webpack": "^5.72.0",
120
120
  "webpack-merge": "^5.7.3",
121
121
  "xregexp": "^2.0.0",
122
- "boring": "^1.1.1",
123
- "postcss-viewport-to-container-toggle": "^2.3.0",
124
- "sanitize-html": "^2.17.2",
125
122
  "@apostrophecms/emulate-mongo-3-driver": "^1.0.6",
123
+ "express-cache-on-demand": "^1.0.4",
126
124
  "broadband": "^1.1.0",
127
- "express-cache-on-demand": "^1.0.4"
125
+ "sanitize-html": "^2.17.2",
126
+ "postcss-viewport-to-container-toggle": "^2.3.0",
127
+ "boring": "^1.1.1"
128
128
  },
129
129
  "devDependencies": {
130
130
  "eslint": "^9.39.1",
@@ -5,7 +5,7 @@
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "../..": {
8
- "version": "4.27.0",
8
+ "version": "4.28.0",
9
9
  "license": "MIT",
10
10
  "dependencies": {
11
11
  "@apostrophecms/emulate-mongo-3-driver": "workspace:^",
@@ -100,7 +100,7 @@
100
100
  "tiny-emitter": "^2.1.0",
101
101
  "tough-cookie": "^4.0.0",
102
102
  "underscore.string": "^3.3.4",
103
- "uploadfs": "^1.25.1",
103
+ "uploadfs": "workspace:^",
104
104
  "void-elements": "^3.1.0",
105
105
  "vue": "^3.5.20",
106
106
  "vue-advanced-cropper": "^2.8.8",
package/test/files.js CHANGED
@@ -133,3 +133,132 @@ describe('Files', function() {
133
133
  });
134
134
 
135
135
  });
136
+
137
+ describe('Files with i18n locale prefixes', function() {
138
+
139
+ let apos;
140
+
141
+ const mockFiles = [
142
+ {
143
+ type: '@apostrophecms/file',
144
+ slug: 'file-locale-test',
145
+ visibility: 'public',
146
+ attachment: {
147
+ type: 'attachment',
148
+ _id: 'testid-locale',
149
+ name: 'localename',
150
+ extension: 'pdf',
151
+ data: 'I am a fake localized PDF'
152
+ }
153
+ }
154
+ ];
155
+
156
+ this.timeout(t.timeout);
157
+
158
+ after(async function() {
159
+ return t.destroy(apos);
160
+ });
161
+
162
+ before(async function() {
163
+ this.timeout(t.timeout);
164
+ this.slow(2000);
165
+
166
+ apos = await t.create({
167
+ root: module,
168
+ modules: {
169
+ '@apostrophecms/file': {
170
+ options: {
171
+ prettyUrls: true
172
+ }
173
+ },
174
+ '@apostrophecms/i18n': {
175
+ options: {
176
+ locales: {
177
+ en: {},
178
+ fr: {
179
+ prefix: '/fr'
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ });
186
+
187
+ apos.baseUrl = apos.http.getBase();
188
+
189
+ try {
190
+ await apos.doc.db.deleteMany({ type: '@apostrophecms/file' });
191
+ try {
192
+ fs.mkdirSync(`${__dirname}/public/uploads/attachments`);
193
+ } catch (e) {
194
+ // May already exist
195
+ }
196
+ } catch (e) {
197
+ assert(false);
198
+ }
199
+
200
+ // Insert the file doc in the default (en) locale
201
+ const req = apos.task.getReq();
202
+ for (const file of mockFiles) {
203
+ await apos.file.insert(req, file);
204
+ const {
205
+ _id, name, extension, data
206
+ } = file.attachment;
207
+ fs.writeFileSync(
208
+ `${__dirname}/public/uploads/attachments/${_id}-${name}.${extension}`,
209
+ data
210
+ );
211
+ }
212
+
213
+ // Localize the file to FR and give it a distinct French slug
214
+ const enFile = await apos.file.find(req, {}).toObject();
215
+ await apos.file.localize(req, enFile, 'fr');
216
+ const frReq = apos.task.getReq({ locale: 'fr' });
217
+ const frDraft = await apos.file.find(frReq.clone({ mode: 'draft' }), {}).toObject();
218
+ frDraft.slug = 'file-test-locale-fr';
219
+ frDraft.title = 'Test Locale FR';
220
+ await apos.file.update(frReq, frDraft);
221
+ });
222
+
223
+ it('should include locale prefix and FR slug in pretty URL for FR locale', async function() {
224
+ const frReq = apos.task.getAnonReq({ locale: 'fr' });
225
+ const files = await apos.file.find(frReq).toArray();
226
+ assert.strictEqual(files.length, 1);
227
+ const file = files[0];
228
+ const attachment = apos.attachment.first(file);
229
+ const url = apos.attachment.url(attachment);
230
+ assert(url);
231
+ // Must use the FR slug (test-locale-fr) and include /fr/ prefix
232
+ assert.strictEqual(
233
+ url,
234
+ `${apos.http.getBase()}/fr/files/test-locale-fr.${attachment.extension}`
235
+ );
236
+ });
237
+
238
+ it('should serve the file at the locale-prefixed pretty URL with FR slug', async function() {
239
+ const frReq = apos.task.getAnonReq({ locale: 'fr' });
240
+ const files = await apos.file.find(frReq).toArray();
241
+ assert.strictEqual(files.length, 1);
242
+ const file = files[0];
243
+ const attachment = apos.attachment.first(file);
244
+ const url = apos.attachment.url(attachment);
245
+ // Verify the locale-prefixed, FR-slug URL actually serves the content
246
+ const body = await apos.http.get(url);
247
+ assert.strictEqual(body, attachment.data);
248
+ });
249
+
250
+ it('should use EN slug without locale prefix for default EN locale', async function() {
251
+ const enReq = apos.task.getAnonReq({ locale: 'en' });
252
+ const files = await apos.file.find(enReq).toArray();
253
+ assert.strictEqual(files.length, 1);
254
+ const file = files[0];
255
+ const attachment = apos.attachment.first(file);
256
+ const url = apos.attachment.url(attachment);
257
+ assert(url);
258
+ // EN keeps its original slug (locale-test) with no locale prefix
259
+ assert.strictEqual(
260
+ url,
261
+ `${apos.http.getBase()}/files/locale-test.${attachment.extension}`
262
+ );
263
+ });
264
+ });