-
-
Notifications
You must be signed in to change notification settings - Fork 177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate docs from Plone 5 to Plone 6 based on TODOs #1917
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -11,8 +11,249 @@ myst: | |||||||||||
|
||||||||||||
# Subscribers (event handlers) | ||||||||||||
|
||||||||||||
```{todo} | ||||||||||||
Move content from: | ||||||||||||
- https://5.docs.plone.org/external/plone.app.dexterity/docs/advanced/event-handlers.html | ||||||||||||
- https://5.docs.plone.org/develop/addons/components/events.html | ||||||||||||
The Zope Component Architecture’s zope.event package is used to manage subscribeable events in Plone. | ||||||||||||
|
||||||||||||
Some of the notable characteristics of the Plone event system are: | ||||||||||||
|
||||||||||||
- It is simple; | ||||||||||||
- Subscriber calling order is not modifiable — you cannot set the order in which event handlers are called; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something like this? Otherwise second part is kind of a dup of the first.
Suggested change
|
||||||||||||
- Events cannot be cancelled — all handlers will always get the event; | ||||||||||||
- Event handlers cannot have return values; | ||||||||||||
- Exceptions raised in an event handler will interrupt the request processing. | ||||||||||||
|
||||||||||||
## Registering an event handler | ||||||||||||
|
||||||||||||
Plone events can be scoped: | ||||||||||||
|
||||||||||||
- Globally (no scope) | ||||||||||||
- Per content type | ||||||||||||
|
||||||||||||
Comment on lines
+28
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
### Example: Register an event-handler on your content type’s creation | ||||||||||||
|
||||||||||||
In your `.product/your/product/configure.zcml` insert: | ||||||||||||
``` | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. append XML for syntax highlighting |
||||||||||||
<subscriber | ||||||||||||
for=".interfaces.IMyContentTypeClass | ||||||||||||
zope.lifecycleevent.IObjectCreatedEvent" | ||||||||||||
handler=".your_python_file.your_method" | ||||||||||||
/> | ||||||||||||
``` | ||||||||||||
The first line defines to which interface you want to bind the execution of your code, which means here, that the code will only be executed if the object is one of your contenttype’s. If you want this to be interface-agnostic, insert an asterix as a wildcard instead. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
The second line defines the event on which this should happen, which is here ‘IObjectCreatedEvent’ – for Archetypes you should use ‘Products.Archetypes.interfaces.IObjectInitializedEvent’ instead. For more available possible events to be used as a trigger, see [Event handlers](https://6.docs.plone.org/backend/subscribers.html#event-handlers) | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No Archetypes anymore.
Suggested change
The URL should not be absolute here I suppose? |
||||||||||||
|
||||||||||||
The third line gives the path to the script that is supposed to be executed. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
Create your `.product/your/product/your_python_file.py` and insert: | ||||||||||||
``` | ||||||||||||
def your_method(object, event): | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its a function and it does not help much, tell the reader what it is: a subscriber
Suggested change
|
||||||||||||
|
||||||||||||
# do sth with your created contenttype | ||||||||||||
Comment on lines
+50
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
``` | ||||||||||||
|
||||||||||||
### Subscribing using ZCML | ||||||||||||
|
||||||||||||
Subscribing to a global event using [ZCML](https://6.docs.plone.org/glossary.html#term-ZCML). | ||||||||||||
``` | ||||||||||||
<subscriber | ||||||||||||
for="Products.PlonePAS.events.UserLoggedOutEvent" | ||||||||||||
handler=".smartcard.clear_extra_cookies_on_logout" | ||||||||||||
/> | ||||||||||||
``` | ||||||||||||
For this event, the Python code in `smartcard.py` would be: | ||||||||||||
``` | ||||||||||||
def clear_extra_cookies_on_logout(event): | ||||||||||||
# What event contains depends on the | ||||||||||||
# triggerer of the event and event class | ||||||||||||
request = event.object.REQUEST | ||||||||||||
... | ||||||||||||
``` | ||||||||||||
Custom event example subscribing to all `IMyEvents` when fired by `IMyObject`: | ||||||||||||
``` | ||||||||||||
<subscriber | ||||||||||||
for=".interfaces.IMyObject | ||||||||||||
.interfaces.IMyEvent" | ||||||||||||
handler=".content.MyObject.myEventHandler" | ||||||||||||
/> | ||||||||||||
``` | ||||||||||||
Life cycle events example: | ||||||||||||
``` | ||||||||||||
<subscriber | ||||||||||||
zcml:condition="installed zope.lifecycleevent" | ||||||||||||
for=".interfaces.ISitsPatient | ||||||||||||
zope.lifecycleevent.IObjectModifiedEvent" | ||||||||||||
handler=".content.SitsPatient.objectModified" | ||||||||||||
/> | ||||||||||||
``` | ||||||||||||
|
||||||||||||
### Subscribing using Python | ||||||||||||
|
||||||||||||
The following subscription is valid through the process life cycle. In unit tests, it is important to clear test event handlers between the test steps. | ||||||||||||
|
||||||||||||
Example: | ||||||||||||
``` | ||||||||||||
import zope.component | ||||||||||||
|
||||||||||||
def my_event_handler(context, event): | ||||||||||||
""" | ||||||||||||
@param context: Zope object for which the event was fired. Usually this is a Plone content object. | ||||||||||||
|
||||||||||||
@param event: Subclass of event. | ||||||||||||
""" | ||||||||||||
pass | ||||||||||||
|
||||||||||||
gsm = zope.component.getGlobalSiteManager() | ||||||||||||
gsm.registerHandler(my_event_handler, (IMyObject,IMyEvent)) | ||||||||||||
``` | ||||||||||||
|
||||||||||||
## Firing an event | ||||||||||||
|
||||||||||||
Use `zope.event.notify()` to fire event objects to their subscribers. | ||||||||||||
|
||||||||||||
Example of how to fire an event in unit tests: | ||||||||||||
``` | ||||||||||||
import zope.event | ||||||||||||
from plone.postpublicationhook.event import AfterPublicationEvent | ||||||||||||
|
||||||||||||
event = AfterPublicationEvent(self.portal, self.portal.REQUEST) | ||||||||||||
zope.event.notify(event) | ||||||||||||
``` | ||||||||||||
|
||||||||||||
## Event types | ||||||||||||
|
||||||||||||
### Creation events | ||||||||||||
|
||||||||||||
`Products.Archetypes.interfaces.IObjectInitializedEvent` is fired for an Archetypes-based object when it’s being initialised; i.e. when it’s being populated for the first time. | ||||||||||||
|
||||||||||||
`Products.Archetypes.interfaces.IWebDAVObjectInitializedEvent` is fired for an Archetypes-based object when it’s being initialised via WebDAV. | ||||||||||||
Comment on lines
+126
to
+128
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove all archetypes |
||||||||||||
|
||||||||||||
`zope.lifecycleevent.IObjectCreatedEvent` is fired for all Zopeish objects when they are being created (they don’t necessarily need to be content objects) or being copied (IObjectCopiedEvent). | ||||||||||||
|
||||||||||||
### Modified events | ||||||||||||
|
||||||||||||
Two different content event types are available and might work differently depending on your scenario: | ||||||||||||
|
||||||||||||
`Products.Archetypes.interfaces.IObjectEditedEvent` | ||||||||||||
called for Archetypes-based objects that are not in the creation stage any more. | ||||||||||||
|
||||||||||||
```{note} | ||||||||||||
`Products.Archetypes.interfaces.IObjectEditedEvent` is fired after `reindexObject()` is called. If you manipulate your content object in a handler for this event, you need to manually reindex new values, or the changes will not be reflected in the `portal_catalog`. | ||||||||||||
``` | ||||||||||||
|
||||||||||||
`zope.lifecycleevent.IObjectModifiedEvent` | ||||||||||||
called for creation-stage events as well, unlike the previous event type. | ||||||||||||
|
||||||||||||
`Products.Archetypes.interfaces.IWebDAVObjectEditedEvent` | ||||||||||||
called for Archetypes-based objects when they are being edited via WebDAV. | ||||||||||||
|
||||||||||||
`Products.Archetypes.interfaces.IEditBegunEvent` | ||||||||||||
called for Archetypes-based objects when an edit operation is begun. | ||||||||||||
|
||||||||||||
`Products.Archetypes.interfaces.IEditCancelledEvent` | ||||||||||||
called for Archetypes-based objects when an edit operation is canceled. | ||||||||||||
|
||||||||||||
|
||||||||||||
### Delete events | ||||||||||||
|
||||||||||||
Delete events can be fired several times for the same object. Some delete event transactions are rolled back. | ||||||||||||
|
||||||||||||
### Copy events | ||||||||||||
|
||||||||||||
`zope.lifecycleevent.IObjectCopiedEvent` is triggered when an object is copied (will also fire IObjectCreatedEvent event code). | ||||||||||||
|
||||||||||||
### Workflow events | ||||||||||||
|
||||||||||||
`Products.DCWorkflow.interfaces.IBeforeTransitionEvent` is triggered before a workflow transition is executed. | ||||||||||||
|
||||||||||||
`Products.DCWorkflow.interfaces.IAfterTransitionEvent` is triggered after a workflow transition has been executed. | ||||||||||||
|
||||||||||||
The DCWorkflow events are low-level events that can tell you a lot about the previous and current states. | ||||||||||||
|
||||||||||||
`Products.CMFCore.interfaces.IActionSucceededEvent` this is a higher level event that is more commonly used to react after a workflow action has completed. | ||||||||||||
|
||||||||||||
### Zope startup events | ||||||||||||
|
||||||||||||
`zope.processlifetime.IProcessStarting` is triggered after component registry has been loaded and Zope is starting up. | ||||||||||||
|
||||||||||||
`zope.processlifetime.IDatabaseOpened` is triggered after the main ZODB database has been opened. | ||||||||||||
|
||||||||||||
## Event handlers | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is more conceptional, right? |
||||||||||||
|
||||||||||||
*Adding custom event handlers for your type* | ||||||||||||
|
||||||||||||
Zope (and so Plone) has a powerful event notification and subscriber subsystem. Events notifications are already fired at several places. | ||||||||||||
|
||||||||||||
With custom subscribers to these events more dynamic functionality can be added. It is possible to react when something happens to objects of a specific type. | ||||||||||||
|
||||||||||||
Zope’s event model is synchronous. When an event is broadcast (via the `notify()` function from the zope.event package) all registered event handlers will be called. This happens for example from the `save` action of an add form, on move or delete of content-objects. There is no guarantee of which order the event handlers will be called in, however. | ||||||||||||
|
||||||||||||
Each event is described by an interface, and will typically carry some information about the event. Some events are known as object events, and provide `zope.component.interfaces.IObjectEvent`. These have an `object` attribute giving access to the (content) object that the event relates to. Object events allow event handlers to be registered for a specific type of object as well as a specific type of event. | ||||||||||||
|
||||||||||||
Some of the most commonly used event types in Plone are shown below. They are all object events. | ||||||||||||
|
||||||||||||
`zope.lifecycleevent.interfaces.IObjectCreatedEvent` fired by the standard add form just after an object has been created, but before it has been added on the container. Note that it is often easier to write a handler for IObjectAddedEvent (see below), because at this point the object has a proper acquisition context. | ||||||||||||
|
||||||||||||
`zope.lifecycleevent.interfaces.IObjectAddedEvent` fired when an object has been added to its container. The container is available as the newParent attribute. The name the new item holds in the container is available as newName. | ||||||||||||
|
||||||||||||
`OFS.interfaces.IObjectWillBeAddedEvent` fired before an object is added to its container. It is also fired on move of an object (copy/paste). | ||||||||||||
|
||||||||||||
`zope.lifecycleevent.interfaces.IObjectModifiedEvent` fired by the standard edit form when an object has been modified. | ||||||||||||
|
||||||||||||
`zope.lifecycleevent.interfaces.IObjectRemovedEvent` fired when an object has been removed from its container. The container is available as the oldParent attribute. The name the item held in the container is available as oldName. | ||||||||||||
|
||||||||||||
`OFS.interfaces.IObjectWillBeRemovedEvent` fired before an object is removed. Until here no deletion has happend. It is also fired on move of an object (copy/paste). | ||||||||||||
|
||||||||||||
`zope.lifecycleevent.interfaces.IObjectMovedEvent` fired when an object is added to, removed from, renamed in, or moved between containers. This event is a super-type of IObjectAddedEvent and IObjectRemovedEvent, shown above. An event handler registered for this interface will be invoked for the ‘added’ and ‘removed’ cases as well. When an object is moved or renamed, all of oldParent, newParent, oldName and newName will be set. | ||||||||||||
|
||||||||||||
`Products.CMFCore.interfaces.IActionSucceededEvent` fired when a workflow event has completed. The `workflow` attribute holds the workflow instance involved, and the `action` attribute holds the action (transition) invoked. | ||||||||||||
|
||||||||||||
Event handlers can be registered using ZCML with the `<subscriber />` directive. | ||||||||||||
|
||||||||||||
As an example, let’s add an event handler to the `Presenter` type. It tries to find users with matching names matching the presenter id, and send these users an email. | ||||||||||||
|
||||||||||||
First, we require an additional import at the top of `presenter.py`: | ||||||||||||
``` | ||||||||||||
from plone import api | ||||||||||||
``` | ||||||||||||
|
||||||||||||
Then, we’ll add the following event subscriber after the schema definition: | ||||||||||||
``` | ||||||||||||
def notifyUser(presenter, event): | ||||||||||||
acl_users = api.portal.get_tool('acl_users') | ||||||||||||
sender = api.portal.get_registry_record('plone.email_from_name') | ||||||||||||
|
||||||||||||
if not sender: | ||||||||||||
return | ||||||||||||
|
||||||||||||
subject = 'Is this you?' | ||||||||||||
message = 'A presenter called {0} was added here {1}'.format( | ||||||||||||
presenter.title, | ||||||||||||
presenter.absolute_url() | ||||||||||||
) | ||||||||||||
|
||||||||||||
matching_users = acl_users.searchUsers(fullname=presenter.title) | ||||||||||||
for user_info in matching_users: | ||||||||||||
email = user_info.get('email', None) | ||||||||||||
if email is not None: | ||||||||||||
api.portal.send_email( | ||||||||||||
recipient=email, | ||||||||||||
sender=sender, | ||||||||||||
subject=subject | ||||||||||||
body=message, | ||||||||||||
) | ||||||||||||
``` | ||||||||||||
|
||||||||||||
And register it in ZCML: | ||||||||||||
|
||||||||||||
- First argument to `for` is an interface describing the object type. | ||||||||||||
- Second argument is the event type. | ||||||||||||
- The arguments to the function reflects these two, so the first argument is the `IPresenter` instance and the second is an `IObjectAddedEvent` instance. | ||||||||||||
|
||||||||||||
``` | ||||||||||||
<subscriber | ||||||||||||
for=".presenter.IPresenter | ||||||||||||
zope.lifecycleevent.interfaces.IObjectAddedEvent" | ||||||||||||
handler=".presenter.notifyUser" | ||||||||||||
/> | ||||||||||||
``` | ||||||||||||
There are many ways to improve this rather simplistic event handler, but it illustrates how events can be used. |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -55,14 +55,94 @@ If Plone is being upgraded at the same time as a Zope version, Plone will usuall | |||||||||||
```{danger} | ||||||||||||
Always back up your Plone site before upgrading. | ||||||||||||
``` | ||||||||||||
A guide to determining what to back up and how to back it up and restore it safely. | ||||||||||||
|
||||||||||||
```{seealso} | ||||||||||||
See Plone 5.2 documentation, [Backing up your Plone deployment](https://5.docs.plone.org/manage/deploying/backup.html). | ||||||||||||
Comment on lines
-59
to
-60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revert. This was deliberate. |
||||||||||||
### Introduction | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
The key rules of backing up a working system are probably: | ||||||||||||
|
||||||||||||
- Back up everything | ||||||||||||
- Maintain multiple generations of backup | ||||||||||||
- Test restoring your backups | ||||||||||||
|
||||||||||||
```{note} | ||||||||||||
This guide assumes that you are already doing this for your system as a whole, and will only cover the considerations specific to Plone. When we say we are assuming you’re already doing this for the system as a whole, what we mean is that your system backup mechanisms - rsync, bacula, whatever - are already backing up the directories into which you’ve installed Plone. | ||||||||||||
``` | ||||||||||||
|
||||||||||||
```{todo} | ||||||||||||
Migrate the Plone 5.2 docs for Backing up your Plone deployment into Plone 6 docs. | ||||||||||||
Your buildout and buildout caches are already backed up, and you’ve tested the restore process. | ||||||||||||
Your remaining consideration is making sure that Plone’s database files are adequately backed up and recoverable. | ||||||||||||
|
||||||||||||
### Objects in motion | ||||||||||||
|
||||||||||||
Objects in motion tend to remain in motion. Objects that are in motion are difficult or impossible to back up accurately. | ||||||||||||
|
||||||||||||
Translation: Plone is a long-lived process that is constantly changing its content database. The largest of these files, the Data.fs filestorage which contains everything except Binary Large OBjects (BLOBs), is always open for writing. The BLOB storage, a potentially complex file hierarchy, is constantly changing and must be referentially synchronized to the filestorage. | ||||||||||||
|
||||||||||||
This means that most system backup schemes are incapable of making useful backups of the content database while it’s in use. We assume you don’t want to stop your Plone site to backup, so you need to add procedures to make sure you have useful backups of Plone’s data. (We assume that you know that the same thing is true of your relational database storage.) | ||||||||||||
|
||||||||||||
### Where’s my data? | ||||||||||||
|
||||||||||||
Your Plone instance installation will contain a `./var` directory (in the same directory as buildout.cfg) that contains the frequently changing data files for the instance. Much of what’s in `./var`, though, is not your actual content database. Rather, it’s log, process id, and socket files. | ||||||||||||
|
||||||||||||
The directories that actually contain content data are: | ||||||||||||
|
||||||||||||
#### Filestorage | ||||||||||||
`./var/filestorage`: | ||||||||||||
|
||||||||||||
This is where Zope Object Database filestorage is maintained. Unless you’ve multiple storages or have changed the name, the key file is Data.fs. It’s typically a large file and contains everything except BLOBS. | ||||||||||||
|
||||||||||||
The other files in filestorage, with extensions like .index, .lock, .old, .tmp are ephemeral, and will be recreated by Zope if they’re absent. | ||||||||||||
|
||||||||||||
#### Blobstorage | ||||||||||||
`./var/blobstorage`: | ||||||||||||
|
||||||||||||
This directory contains a deeply nested directory hierarchy that, in turn, contains the BLOBs of your database: PDFs, image files, office automation files and such. | ||||||||||||
|
||||||||||||
The key thing to know about filestorage and blobstorage is that they are maintained synchronously. The filestorage has references to BLOBs in the blobstorage. If the two storages are not synchronized, you’ll get errors. | ||||||||||||
|
||||||||||||
### collective.recipe.backup | ||||||||||||
|
||||||||||||
[collective.recipe.backup](https://pypi.python.org/pypi/collective.recipe.backup) is a well-maintained and well-supported recipe for solving the “objects in motion” problem for a live Plone database. It makes it easy to both back up and restore the object database. The recipe is basically a sophisticated wrapper around `repozo`, a Zope database backup tool, and `rsync`, the common file synchronization tool. | ||||||||||||
|
||||||||||||
If you’re using any of Plone’s installation kits, `collective.recipe.backup` is included in your install. If not, you may add it to your buildout by adding a `backup` part: | ||||||||||||
|
||||||||||||
``` | ||||||||||||
[buildout] | ||||||||||||
parts = | ||||||||||||
... | ||||||||||||
backup | ||||||||||||
... | ||||||||||||
|
||||||||||||
[backup] | ||||||||||||
recipe = collective.recipe.backup | ||||||||||||
``` | ||||||||||||
|
||||||||||||
There are several useful option settings for the recipe, all set by adding configuration information. All are documented on [the PyPI page](https://pypi.python.org/pypi/collective.recipe.backup). Perhaps the most useful is the `location` option, which sets the destination for backup files: | ||||||||||||
|
||||||||||||
``` | ||||||||||||
[backup] | ||||||||||||
recipe = collective.recipe.backup | ||||||||||||
location = /path/to/reliably/attached/storage/filestorage | ||||||||||||
blobbackuplocation = /path/to/reliably/attached/storage/blobstorage | ||||||||||||
``` | ||||||||||||
|
||||||||||||
If this is unspecified, the backup destination is the buildout var directory. The backup destination, though, may be any reliably attached location - including another partition, drive or network storage. | ||||||||||||
|
||||||||||||
### Operation | ||||||||||||
|
||||||||||||
Once you’ve run buildout, you’ll have `bin/backup` and `bin/restore` scripts in your buildout. Since all options are set via buildout, there are few command-line options, and operation is generally as simple as using the bare commands. `bin/restore` will accept a date-time argument if you’re keeping multiple backups. See the docs for details. | ||||||||||||
|
||||||||||||
Backup operations may be run without stopping Plone. Restore operations require that you stop Plone, then restart after the restore is complete. | ||||||||||||
|
||||||||||||
`bin/backup` is commonly included in a cron table for regular operation. Make sure you test backup/restore before relying on it. | ||||||||||||
|
||||||||||||
### Incremental backups | ||||||||||||
|
||||||||||||
`collective.recipe.backup` offers both incremental and full backup and will maintain multiple generations of backups. Tune these to meet your needs. | ||||||||||||
|
||||||||||||
When incremental backup is enabled, doing a database packing operation will automatically cause the next backup to be a full backup. | ||||||||||||
|
||||||||||||
If your backup continuity needs are extreme, your incremental backup may be equally extreme. There are Plone installations where incremental backups are run every few minutes. | ||||||||||||
|
||||||||||||
|
||||||||||||
(upgrade-setup-a-test-environment-to-rehearse-the-upgrade-label)= | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,6 +59,10 @@ You or your development tools, such as GNU Make, must perform that step. | |
|
||
## Manage frontend Node.js packages | ||
|
||
```{todo} | ||
Why do we use pnpm? | ||
``` | ||
Comment on lines
-62
to
-64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revert, create an issue if one does not already exist either in Documentation or Volto repo, and add its link here. |
||
[pnpm](https://pnpm.io/) is a fast, storage-efficient package manager with built-in workspace support, making it ideal for monorepos. | ||
|
||
- Fast installs – Links dependencies instead of duplicating them, reducing install times. | ||
- Storage efficiency – Uses a content-addressable store, sharing packages globally to save disk space. | ||
- Built-in workspaces – `pnpm workspaces` allow seamless management of multiple packages in a monorepo. | ||
- Strict dependency isolation – Prevents accidental usage of undeclared dependencies. | ||
- Optimized CI/CD – Faster caching and reduced network requests improve build performance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I realized the original description was not clear. I updated it just now. Please revert changes in this file. Instead set the configuration values in
conf.py
, changing code from the following...documentation/docs/conf.py
Lines 381 to 385 in b4462a8
...to the following.
Ref: https://www.sphinx-doc.org/en/master/usage/extensions/todo.html#configuration
Since this TODO already contains a link to an existing issue, we do not need to create an issue or add a link to the issue.