@danypops/pi-packed 0.27.2 → 0.27.3
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/package.json
CHANGED
|
@@ -161,6 +161,39 @@ function resolveDependencyCandidate(depDir: string, depName: string): ResolvedDa
|
|
|
161
161
|
return undefined;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/** Strips a leading "pi-" from an unscoped package name, e.g. "pi-papyrus" -> "papyrus", "pipes" ->
|
|
165
|
+
* "pipes" unchanged. Mirrors the real, universal naming convention every pi-* extension in this
|
|
166
|
+
* house follows for its own namesake daemon package (pi-lector/lector, pi-jittor/jittor,
|
|
167
|
+
* pi-tickets/tickets, pi-pipes/pipes, pi-web-spider/web-spider, pi-papyrus/papyrus). */
|
|
168
|
+
function namesakeDaemonName(unscopedPackageName: string): string | undefined {
|
|
169
|
+
return unscopedPackageName.startsWith("pi-") ? unscopedPackageName.slice("pi-".length) : undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Resolves every dependency-name candidate for one dependency, across both its nested and
|
|
173
|
+
* hoisted layout (see resolveDependencyCandidate) -- factored out so detectVehicleDaemonService
|
|
174
|
+
* can collect candidates across EVERY dependency name before picking one, instead of returning
|
|
175
|
+
* on the first name that resolves at all. */
|
|
176
|
+
function resolveDependencyNameCandidate(
|
|
177
|
+
packageDir: string,
|
|
178
|
+
depName: string,
|
|
179
|
+
hoistedNodeModulesDir: string | undefined,
|
|
180
|
+
): ResolvedDaemonEntrypoint | undefined {
|
|
181
|
+
const candidateDirs = [join(packageDir, "node_modules", depName), ...(hoistedNodeModulesDir ? [join(hoistedNodeModulesDir, depName)] : [])];
|
|
182
|
+
const resolved = candidateDirs
|
|
183
|
+
.map((depDir) => resolveDependencyCandidate(depDir, depName))
|
|
184
|
+
.filter((entry): entry is ResolvedDaemonEntrypoint => entry !== undefined);
|
|
185
|
+
if (resolved.length === 0) return undefined;
|
|
186
|
+
if (resolved.length === 1) return resolved[0];
|
|
187
|
+
// Nested and hoisted both resolved for the same dependency (the real jittor incident:
|
|
188
|
+
// a stale nested copy always won just because it's checked first). Prefer the newer
|
|
189
|
+
// version; a genuine tie keeps the nested result, preserving papyrus's intentional case.
|
|
190
|
+
let best = resolved[0]!;
|
|
191
|
+
for (const candidate of resolved.slice(1)) {
|
|
192
|
+
if (versionAtLeast(candidate.version, best.version) && candidate.version !== best.version) best = candidate;
|
|
193
|
+
}
|
|
194
|
+
return best;
|
|
195
|
+
}
|
|
196
|
+
|
|
164
197
|
export function detectVehicleDaemonService(
|
|
165
198
|
packageDir: string,
|
|
166
199
|
fallbackName: string,
|
|
@@ -174,26 +207,24 @@ export function detectVehicleDaemonService(
|
|
|
174
207
|
return { binPath: join(packageDir, ownBin), args: ["serve"], name: unscopedName(own.name ?? fallbackName), version: own.version };
|
|
175
208
|
}
|
|
176
209
|
|
|
210
|
+
// A pi-X package can carry OTHER Vehicle-shaped dependencies for unrelated reasons (e.g.
|
|
211
|
+
// pi-papyrus depends on @danypops/jittor only for Context Hub types) -- confirmed live: a
|
|
212
|
+
// naive "first dependency name that resolves" scan returned jittor's spec for pi-papyrus
|
|
213
|
+
// (dependencies happened to list "@danypops/jittor" before "@danypops/papyrus"), silently
|
|
214
|
+
// mis-registering pi-papyrus's own Armada vehicle as another package's daemon entirely. Collect
|
|
215
|
+
// every resolvable candidate across ALL dependency names first, then prefer the one whose own
|
|
216
|
+
// resolved vehicle name matches this package's namesake convention (pi-X -> X) before falling
|
|
217
|
+
// back to the first-found candidate for a package that doesn't follow that convention.
|
|
218
|
+
const byName = unscopedName(own.name ?? fallbackName);
|
|
219
|
+
const wantedNamesake = namesakeDaemonName(byName);
|
|
220
|
+
const candidates: ResolvedDaemonEntrypoint[] = [];
|
|
177
221
|
for (const depName of Object.keys(own.dependencies ?? {})) {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const resolved = candidateDirs
|
|
183
|
-
.map((depDir) => resolveDependencyCandidate(depDir, depName))
|
|
184
|
-
.filter((entry): entry is ResolvedDaemonEntrypoint => entry !== undefined);
|
|
185
|
-
if (resolved.length === 0) continue;
|
|
186
|
-
if (resolved.length === 1) return resolved[0];
|
|
187
|
-
// Nested and hoisted both resolved for the same dependency (the real jittor incident:
|
|
188
|
-
// a stale nested copy always won just because it's checked first). Prefer the newer
|
|
189
|
-
// version; a genuine tie keeps the nested result, preserving papyrus's intentional case.
|
|
190
|
-
let best = resolved[0]!;
|
|
191
|
-
for (const candidate of resolved.slice(1)) {
|
|
192
|
-
if (versionAtLeast(candidate.version, best.version) && candidate.version !== best.version) best = candidate;
|
|
193
|
-
}
|
|
194
|
-
return best;
|
|
222
|
+
const resolved = resolveDependencyNameCandidate(packageDir, depName, hoistedNodeModulesDir);
|
|
223
|
+
if (!resolved) continue;
|
|
224
|
+
if (wantedNamesake !== undefined && resolved.name === wantedNamesake) return resolved;
|
|
225
|
+
candidates.push(resolved);
|
|
195
226
|
}
|
|
196
|
-
return
|
|
227
|
+
return candidates[0];
|
|
197
228
|
}
|
|
198
229
|
|
|
199
230
|
/**
|
|
@@ -189,6 +189,38 @@ describe("resolveDaemonServiceSpec", () => {
|
|
|
189
189
|
expect(result.spec.name).toBe("papyrus");
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
+
it("prefers the dependency matching its own pi-X -> X namesake convention over an unrelated Vehicle-shaped dependency listed first -- the real live incident: pi-papyrus's own dependency scan resolved to jittor (a Context Hub-types-only dependency) instead of papyrus, because 'jittor' sorted before 'papyrus' in the dependencies object", () => {
|
|
193
|
+
const piHome = fakePiHome();
|
|
194
|
+
const extDir = join(piHome, "npm", "node_modules", "@danypops/pi-papyrus");
|
|
195
|
+
// Real on-disk order: @danypops/jittor genuinely does sort before @danypops/papyrus.
|
|
196
|
+
writeRawPackage(extDir, {
|
|
197
|
+
name: "@danypops/pi-papyrus",
|
|
198
|
+
version: "1.0.0",
|
|
199
|
+
dependencies: { "@danypops/jittor": "^0.18.0", "@danypops/papyrus": "^0.54.0" },
|
|
200
|
+
});
|
|
201
|
+
const jittorDir = join(extDir, "node_modules", "@danypops/jittor");
|
|
202
|
+
writeRawPackage(jittorDir, {
|
|
203
|
+
name: "@danypops/jittor",
|
|
204
|
+
version: "0.18.0",
|
|
205
|
+
bin: { jittor: "src/cli.ts" },
|
|
206
|
+
dependencies: { "@danypops/vehicle-server": "^0.24.0" },
|
|
207
|
+
});
|
|
208
|
+
const papyrusDir = join(extDir, "node_modules", "@danypops/papyrus");
|
|
209
|
+
writeRawPackage(papyrusDir, {
|
|
210
|
+
name: "@danypops/papyrus",
|
|
211
|
+
version: "0.54.0",
|
|
212
|
+
bin: { papyrus: "src/cli.ts" },
|
|
213
|
+
dependencies: { "@danypops/vehicle-server": "^0.24.0" },
|
|
214
|
+
packed: { daemonService: { binPath: "src/cli.ts", args: ["serve"], handleFilename: "vehicle-handle.json" } },
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const result = resolveDaemonServiceSpec(piHome, "npm:@danypops/pi-papyrus");
|
|
218
|
+
expect(result.ok).toBe(true);
|
|
219
|
+
if (!result.ok) throw new Error("expected ok");
|
|
220
|
+
expect(result.spec.name).toBe("papyrus");
|
|
221
|
+
expect(result.spec.binPath).toBe(join(papyrusDir, "src/cli.ts"));
|
|
222
|
+
});
|
|
223
|
+
|
|
192
224
|
it("honors a nested dependency's own explicit packed.daemonService manifest, not just bin+dependency convention -- confirmed live: papyrus's real handle file is 'vehicle-handle.json', not the 'daemon.json' convention guess, and papyrus is only ever discovered this way (one level into pi-papyrus's own dependencies), never installed directly by name", () => {
|
|
193
225
|
const piHome = fakePiHome();
|
|
194
226
|
const extDir = join(piHome, "npm", "node_modules", "@danypops/pi-papyrus");
|