dropsh 0.5.6 → 0.5.7

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.
@@ -65,9 +65,12 @@ async function resolveDescriptor(entry, bases, declaredBy) {
65
65
  * cache miss, so a shared dependency's factory runs exactly once (AC-3). Because
66
66
  * the key is the *resolved* module, one relative string naming different files
67
67
  * on a path is not a cycle, and two same-package entries with different options
68
- * are two distinct plugins. `id` collisions are still de-duplicated in the final
69
- * list. A dependency cycle is rejected with the offending chain of descriptor
70
- * names. Pre-constructed plugin objects pass through unchanged (backward compat).
68
+ * are two distinct plugins — even when they share a constant `plugin.id`
69
+ * (DROPSH-12), because descriptor de-dup keys on that identity, not `plugin.id`.
70
+ * Pre-constructed plugin objects (and `composePlugins` children) carry no such
71
+ * identity, so those are still de-duplicated by `plugin.id` in the final list. A
72
+ * dependency cycle is rejected with the offending chain of descriptor names.
73
+ * Pre-constructed plugin objects pass through unchanged (backward compat).
71
74
  */
72
75
  async function resolvePlugins(raw, configPath) {
73
76
  if (!Array.isArray(raw))
@@ -83,10 +86,18 @@ async function resolvePlugins(raw, configPath) {
83
86
  // Descriptors on the current DFS path, keyed by resolved identity; `name` is
84
87
  // the descriptor string, kept only to render the cycle-error chain.
85
88
  const stack = [];
86
- const emit = (plugin) => {
87
- if (doneIds.has(plugin.id))
88
- return;
89
- doneIds.add(plugin.id);
89
+ // Descriptor-constructed plugins are already de-duplicated by their descriptor
90
+ // identity (`doneKeys`, computed in `expand`), so `emit` must not also collapse
91
+ // them by `plugin.id`: two profiles of the same plugin can legitimately share a
92
+ // constant `plugin.id` while being distinct instances (DROPSH-12). Only
93
+ // pre-constructed plugins (and `composePlugins` children) carry no descriptor
94
+ // identity, so those are still de-duplicated by `plugin.id`.
95
+ const emit = (plugin, dedupById) => {
96
+ if (dedupById) {
97
+ if (doneIds.has(plugin.id))
98
+ return;
99
+ doneIds.add(plugin.id);
100
+ }
90
101
  out.push(plugin);
91
102
  };
92
103
  const expandDeps = async (plugin, bases) => {
@@ -96,7 +107,7 @@ async function resolvePlugins(raw, configPath) {
96
107
  // Expand a pre-constructed plugin (or a nested array of them): a composite —
97
108
  // e.g. `composePlugins(a(), b())` — reaches here as an array, and is flattened
98
109
  // into separate entries so N children register without hand-merging hooks.
99
- const expandValue = async (value, bases) => {
110
+ const expandValue = async (value, bases, dedupById) => {
100
111
  if (Array.isArray(value)) {
101
112
  for (const sub of value)
102
113
  await expand(sub, bases, undefined);
@@ -104,12 +115,13 @@ async function resolvePlugins(raw, configPath) {
104
115
  }
105
116
  const plugin = value;
106
117
  await expandDeps(plugin, bases);
107
- emit(plugin);
118
+ emit(plugin, dedupById);
108
119
  };
109
120
  async function expand(entry, bases, declaredBy) {
110
121
  if (!isPluginDescriptor(entry)) {
111
- // Pre-constructed plugin object, or a nested-array composite entry.
112
- await expandValue(entry, bases);
122
+ // Pre-constructed plugin object, or a nested-array composite entry: no
123
+ // descriptor identity, so de-duplicate it by `plugin.id`.
124
+ await expandValue(entry, bases, true);
113
125
  return;
114
126
  }
115
127
  const { resolvedPath, exportName, construct } = await resolveDescriptor(entry, bases, declaredBy);
@@ -126,7 +138,10 @@ async function resolvePlugins(raw, configPath) {
126
138
  try {
127
139
  // Child descriptors resolve relative to this plugin's module first.
128
140
  const childBases = [pathToFileURL(resolvedPath).href, ...baseBases];
129
- await expandValue(produced, childBases);
141
+ // Descriptor-constructed: its identity is tracked by `doneKeys`, so it is
142
+ // emitted without `plugin.id` de-dup (a returned array's pre-constructed
143
+ // children still de-dup by id via the non-descriptor path above).
144
+ await expandValue(produced, childBases, false);
130
145
  }
131
146
  finally {
132
147
  stack.pop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dropsh",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "Entity-agnostic helper CLI for Drupal 11 JSON:API, used by the dropsh Claude skill for editorial publishing workflows.",
5
5
  "type": "module",
6
6
  "bin": {