fix(packages): load bundled Pi runtime for local installs
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# Task: Fix packaged local Skill installation
|
||||
|
||||
## Identity
|
||||
|
||||
- Task ID: 20260902-local-skill-install-fix-6b3e91a4
|
||||
- Mode: Feature
|
||||
- Branch: codex/20260902-local-skill-install-fix-6b3e91a4-local-skill-install-fix
|
||||
- Worktree: D:\Datas\OthersProjects\makelore-local-skill-install-fix-6b3e91a4
|
||||
- Base commit: 28e1690038d61b9333a2f6ca77f0187ea4273676
|
||||
- Owner: codex-root
|
||||
- Status: In Progress
|
||||
|
||||
## Scope
|
||||
|
||||
- Fix packaged conversation-driven Device Package installation after the bundled
|
||||
Pi CLI succeeds but the Main-owned package inspection path fails to load its
|
||||
production dependency closure.
|
||||
- Add a red/green packaged-runtime regression for the lazy package-manager
|
||||
boundary.
|
||||
- Preserve closed Device Package errors across the Pi extension bridge so
|
||||
supported-source dependency failures and unsupported source syntax no longer
|
||||
collapse to the same generic `Bridge request failed` response.
|
||||
- Rebuild and verify the Windows `app.asar`/product artifact, then execute the
|
||||
real packaged npm and Git prepare paths without committing a package.
|
||||
|
||||
## Intent And Constraints
|
||||
|
||||
- Keep the accepted Device Packages product model unchanged: installation is
|
||||
initiated only through conversation tools; preview and a distinct later
|
||||
confirmation precede commit; packages are local Main-owned immutable
|
||||
generations; lifecycle scripts stay disabled; only future/idle parent workers
|
||||
receive resources and child workers remain empty.
|
||||
- Work only in the isolated feature worktree from exact client `main`
|
||||
`28e1690038d61b9333a2f6ca77f0187ea4273676`. Preserve the three unowned task
|
||||
records in the client root and do not reset, stash, clean, adopt, or modify
|
||||
that worktree.
|
||||
- Do not add a global Pi CLI requirement, visible installer, Account Library,
|
||||
Marketplace Release/Admission, hosted Web Search, Provider, server, billing,
|
||||
deploy, publish, push, or PR behavior.
|
||||
- Avoid leaking local paths, stacks, credentials, or raw dependency diagnostics
|
||||
to Pi/Renderer. Only closed Device Package codes and bounded public messages
|
||||
may cross the bridge; internal errors may be logged in Main.
|
||||
- The prior diagnosis is accepted evidence: installed bundled Pi npm/Git
|
||||
resolution succeeds, while importing the packaged
|
||||
`@earendil-works/pi-coding-agent` graph fails because
|
||||
`partial-json@0.1.7` is absent from `app.asar`.
|
||||
|
||||
## Concurrent Task Gate
|
||||
|
||||
- `check_project_docs` passed for the client root.
|
||||
- `task_context start` created the exact isolated worktree/branch/base recorded
|
||||
above; `status --json` confirmed matching task, owner, mode, branch, worktree,
|
||||
and base.
|
||||
- The client root remains on `main` with exactly three pre-existing untracked
|
||||
task records; none was copied as a change or adopted.
|
||||
- The only semantically adjacent registered peer is the older native Web Search
|
||||
client coordinator. Its task record owns hosted/model-tool composition, not
|
||||
Device Package manager loading, bridge error projection, or packaging closure;
|
||||
no semantic or file-ownership conflict is present.
|
||||
- Gate result: Passed.
|
||||
|
||||
## Project Context Loaded
|
||||
|
||||
Read the project memory entry, active record, positioning/current state,
|
||||
decision index, ADR-006, system/module/data-flow architecture, business rules,
|
||||
success criteria, evidence/reflection/commitment/stale indexes, the completed
|
||||
Model Tools and Device Packages integration record, the relevant peer record,
|
||||
and the prior diagnosis record.
|
||||
|
||||
Relevant understanding:
|
||||
|
||||
- Electron Main owns package bytes, inspection, durable generations, Pi resource
|
||||
materialization, and packaged dependency authority; Renderer/Pi receive only
|
||||
closed projections.
|
||||
- The current integrated frontier deliberately lazy-loads the ESM-only Pi package
|
||||
manager for remote installs. Workspace tests passed, but packaged signed-in
|
||||
installation remained an explicit acceptance gate and the installed artifact
|
||||
now proves that its ESM dependency closure is incomplete.
|
||||
- ADR-006 explicitly requires installed-package regressions to be exercised
|
||||
against final `app.asar` and packaged runtime roots.
|
||||
- Likely owned modules are `electron/coding-packages/**`, the Pi extension bridge,
|
||||
artifact verification scripts/tests, and the root production dependency lock.
|
||||
- Planning Gate result: Passed.
|
||||
|
||||
## Outcome
|
||||
|
||||
- Replaced the packaged Device Package inspection dependency on the incomplete
|
||||
`app.asar` Pi graph with the already-distributed physical
|
||||
`resources/pi-runtime/dist/index.js` authority used by the bundled Pi CLI.
|
||||
- Preserved closed `local_package_*` failures through Main's worker bridge and
|
||||
the generated Pi extension while keeping unknown failures generic.
|
||||
- Added an artifact gate that rejects a Main-reachable bare
|
||||
`@earendil-works/pi-coding-agent` dynamic import and proves that the packaged
|
||||
physical runtime exposes `DefaultPackageManager` and
|
||||
`SettingsManager.inMemory`.
|
||||
- Bumped the generated MakeLore Pi extension generation so new workers cannot
|
||||
reuse stale bridge code.
|
||||
- Windows product packaging and real packaged-source acceptance remain in
|
||||
progress.
|
||||
|
||||
## Verification
|
||||
|
||||
- Installed-artifact RED: importing the installed `app.asar` Pi graph failed
|
||||
with `ERR_MODULE_NOT_FOUND` for `partial-json`; importing its physical
|
||||
`resources/pi-runtime/dist/index.js` succeeded with both required exports.
|
||||
- TDD RED: the new Device Package manager regression attempted the unavailable
|
||||
app Pi graph; the real worker bridge reduced a closed failure to
|
||||
`Bridge request failed`; the artifact gate accepted the stale bare import.
|
||||
- Focused and adjacent unit tests: 8 files, 69 passed.
|
||||
- Full unit suite: 222 files, 1815 passed, 2 skipped; pressure test 1 passed.
|
||||
The first run saw two Windows `spawn EBUSY` failures while Electron was first
|
||||
downloaded; that file then passed 3/3 alone and the complete rerun passed.
|
||||
- `pnpm typecheck`: passed.
|
||||
- Scoped ESLint for every changed TypeScript/JavaScript source and test: passed.
|
||||
- `pnpm run build:vite`: passed; generated Main contains the physical runtime
|
||||
loader and no bare dynamic import of `@earendil-works/pi-coding-agent`.
|
||||
- `git diff --check`: passed.
|
||||
|
||||
## Follow-ups
|
||||
|
||||
- None recorded.
|
||||
|
||||
## Promotion Candidates
|
||||
|
||||
- None recorded.
|
||||
@@ -1,6 +1,7 @@
|
||||
import { execFile } from 'node:child_process';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { cp, mkdir, readFile, rename, rm, stat } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import { atomicWriteJson } from '../coding-projects/atomic-json';
|
||||
import {
|
||||
@@ -53,6 +54,17 @@ export interface DevicePackageInstallInput {
|
||||
|
||||
export type DevicePackageInstallRunner = (input: DevicePackageInstallInput) => Promise<void>;
|
||||
|
||||
interface PiPackageManagerModule {
|
||||
DefaultPackageManager: new (input: {
|
||||
cwd: string;
|
||||
agentDir: string;
|
||||
settingsManager: unknown;
|
||||
}) => { getInstalledPath(source: string, scope: 'user'): string | undefined };
|
||||
SettingsManager: {
|
||||
inMemory(user: Record<string, unknown>, project: { projectTrusted: boolean }): unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DevicePackageManagerOptions {
|
||||
rootDir: string;
|
||||
executablePath?: string;
|
||||
@@ -208,6 +220,23 @@ async function gitHead(directory: string): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
async function loadBundledPiPackageManager(cliPath: string): Promise<PiPackageManagerModule> {
|
||||
const entryPath = path.join(path.dirname(path.resolve(cliPath)), 'index.js');
|
||||
try {
|
||||
const loaded = createRequire(entryPath)(entryPath) as Partial<PiPackageManagerModule>;
|
||||
if (typeof loaded.DefaultPackageManager !== 'function'
|
||||
|| typeof loaded.SettingsManager?.inMemory !== 'function') {
|
||||
throw new Error('Pi package manager exports are unavailable');
|
||||
}
|
||||
return loaded as PiPackageManagerModule;
|
||||
} catch {
|
||||
throw new DevicePackageError(
|
||||
'local_package_dependency_failed',
|
||||
'Bundled Pi package manager is unavailable',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class DevicePackageManager {
|
||||
private readonly rootDir: string;
|
||||
private readonly packagesDir: string;
|
||||
@@ -270,18 +299,23 @@ export class DevicePackageManager {
|
||||
}
|
||||
const agentDir = path.join(planRoot, 'pi-agent');
|
||||
const cwd = path.join(planRoot, 'project');
|
||||
await this.runInstall({
|
||||
source: source.spec,
|
||||
executablePath,
|
||||
cliPath,
|
||||
...(this.options.npmCliPath ? { npmCliPath: this.options.npmCliPath } : {}),
|
||||
agentDir,
|
||||
cwd,
|
||||
env: installEnvironment(agentDir),
|
||||
});
|
||||
const { DefaultPackageManager, SettingsManager } = await import(
|
||||
'@earendil-works/pi-coding-agent'
|
||||
);
|
||||
try {
|
||||
await this.runInstall({
|
||||
source: source.spec,
|
||||
executablePath,
|
||||
cliPath,
|
||||
...(this.options.npmCliPath ? { npmCliPath: this.options.npmCliPath } : {}),
|
||||
agentDir,
|
||||
cwd,
|
||||
env: installEnvironment(agentDir),
|
||||
});
|
||||
} catch {
|
||||
throw new DevicePackageError(
|
||||
'local_package_dependency_failed',
|
||||
'Pi could not install this package source',
|
||||
);
|
||||
}
|
||||
const { DefaultPackageManager, SettingsManager } = await loadBundledPiPackageManager(cliPath);
|
||||
const manager = new DefaultPackageManager({
|
||||
cwd,
|
||||
agentDir,
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { PiProductTools } from './product-tools';
|
||||
import type { CodingPluginToolDefinition } from '../../../shared/coding-plugins';
|
||||
import type { PiSkillEntry } from './resource-loader';
|
||||
import type { EffectivePluginSnapshot } from '../../coding-plugins/effective-resolver';
|
||||
import { DevicePackageError } from '../../coding-packages/device-package-manager';
|
||||
|
||||
const MAX_REQUEST_BYTES = 64 * 1024;
|
||||
const PRODUCT_TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9._:-]{0,63}$/u;
|
||||
@@ -483,7 +484,14 @@ export class PiManagedExtensionHost {
|
||||
response.removeListener('close', cancel);
|
||||
record.waiters.delete(value.resourceId);
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
if (!response.writableEnded && error instanceof DevicePackageError) {
|
||||
this.respond(response, 400, {
|
||||
code: error.code,
|
||||
error: error.message.slice(0, 512),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!response.writableEnded) this.respond(response, 400, { error: 'Bridge request failed' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from 'node:path';
|
||||
import { atomicWriteText } from '../../../coding-projects/atomic-json';
|
||||
|
||||
export const MAKELORE_PI_EXTENSION_VERSION = 4;
|
||||
export const MAKELORE_PI_EXTENSION_VERSION = 5;
|
||||
export const MAKELORE_PI_EXTENSION_FILENAME = `makelore-runtime-v${MAKELORE_PI_EXTENSION_VERSION}.mjs`;
|
||||
|
||||
const BUNDLE_SOURCE = String.raw`
|
||||
@@ -93,7 +93,15 @@ export function createMakeloreRuntime(runtimeDefaults = {}) {
|
||||
signal,
|
||||
});
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok) throw new Error(result.error || 'Makelore runtime bridge rejected the request');
|
||||
if (!response.ok) {
|
||||
const message = typeof result.error === 'string' && result.error
|
||||
? result.error
|
||||
: 'Makelore runtime bridge rejected the request';
|
||||
const code = typeof result.code === 'string' && /^local_package_[a-z_]+$/.test(result.code)
|
||||
? result.code
|
||||
: '';
|
||||
throw new Error(code ? code + ': ' + message : message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -471,6 +471,28 @@ export async function verifyPackagedMarketplaceClientArtifact(appAsar) {
|
||||
return verifyMarketplaceClientArtifact(reachableContents, verifiedTrustSource);
|
||||
}
|
||||
|
||||
export async function verifyPackagedDevicePackageRuntime(appAsar, runtimePlatform) {
|
||||
const { reachable } = await readPackagedApplicationGraph(appAsar);
|
||||
const staleImports = reachable
|
||||
.filter(({ source }) => /\bimport\(\s*["']@earendil-works\/pi-coding-agent["']\s*\)/u.test(source))
|
||||
.map(({ entry }) => entry);
|
||||
if (staleImports.length > 0) {
|
||||
throw new Error(
|
||||
`Packaged Device Package manager still imports the incomplete app graph: ${staleImports.join(', ')}`,
|
||||
);
|
||||
}
|
||||
if (runtimePlatform.devicePackageManager?.defaultPackageManager !== 'function'
|
||||
|| runtimePlatform.devicePackageManager?.settingsManager !== 'function') {
|
||||
throw new Error('Packaged Pi runtime cannot load the Device Package manager');
|
||||
}
|
||||
return {
|
||||
authority: 'bundled-pi-runtime',
|
||||
appAsarRootImport: false,
|
||||
exports: runtimePlatform.devicePackageManager,
|
||||
result: 'pass',
|
||||
};
|
||||
}
|
||||
|
||||
async function filesContainingNeedles(root, needles) {
|
||||
const matches = [];
|
||||
const visit = async (path) => {
|
||||
@@ -496,12 +518,21 @@ async function inspectProductRuntime(executable, resourcesDirectory) {
|
||||
const resources = process.env.MAKELORE_PI_PRODUCT_RESOURCES;
|
||||
const appRequire = createRequire(path.join(resources, 'app.asar', 'package.json'));
|
||||
const packagedPackage = appRequire('./package.json');
|
||||
const piRuntime = require(path.join(resources, 'pi-runtime', 'dist', 'index.js'));
|
||||
if (typeof piRuntime.DefaultPackageManager !== 'function'
|
||||
|| typeof piRuntime.SettingsManager?.inMemory !== 'function') {
|
||||
throw new Error('Bundled Pi package manager exports are unavailable');
|
||||
}
|
||||
process.stdout.write(JSON.stringify({
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
node: process.versions.node,
|
||||
electron: process.versions.electron,
|
||||
packagedPackage,
|
||||
devicePackageManager: {
|
||||
defaultPackageManager: typeof piRuntime.DefaultPackageManager,
|
||||
settingsManager: typeof piRuntime.SettingsManager.inMemory,
|
||||
},
|
||||
}));
|
||||
`;
|
||||
const { stdout } = await runCommand(executable, ['-e', script], {
|
||||
@@ -744,6 +775,7 @@ export async function verifyPiProductArtifact({ projectRoot, executable }) {
|
||||
appAsarContents,
|
||||
});
|
||||
const marketplace = await verifyPackagedMarketplaceClientArtifact(appAsar);
|
||||
const devicePackages = await verifyPackagedDevicePackageRuntime(appAsar, runtimePlatform);
|
||||
const actualSkills = bundledPluginResources.coreResources.skills;
|
||||
const missingExtensionMarkers = EXTENSION_CONTRACT_MARKERS.filter(
|
||||
(marker) => !appAsarContents.includes(Buffer.from(marker)),
|
||||
@@ -797,6 +829,7 @@ export async function verifyPiProductArtifact({ projectRoot, executable }) {
|
||||
packagedClosure,
|
||||
bundledPlugins: bundledPluginResources,
|
||||
marketplace,
|
||||
devicePackages,
|
||||
extension: {
|
||||
contractMarkers: EXTENSION_CONTRACT_MARKERS,
|
||||
packaged: true,
|
||||
|
||||
@@ -118,12 +118,24 @@ describe('DevicePackageManager', () => {
|
||||
expect(preview.warnings.join(' ')).toContain('网络');
|
||||
});
|
||||
|
||||
it('runs remote Pi installation with lifecycle scripts disabled and reports declared scripts', async () => {
|
||||
it('uses the bundled Pi runtime for remote installation when the app Pi graph is unavailable', async () => {
|
||||
const root = await temporaryRoot('remote');
|
||||
const runtimeRoot = path.join(root, 'pi-runtime');
|
||||
const cliPath = path.join(runtimeRoot, 'dist', 'cli.js');
|
||||
await writeJson(path.join(runtimeRoot, 'package.json'), { type: 'module' });
|
||||
await mkdir(path.dirname(cliPath), { recursive: true });
|
||||
await writeFile(path.join(path.dirname(cliPath), 'index.js'), [
|
||||
"import path from 'node:path';",
|
||||
'export class SettingsManager { static inMemory() { return {}; } }',
|
||||
'export class DefaultPackageManager {',
|
||||
' constructor(options) { this.agentDir = options.agentDir; }',
|
||||
" getInstalledPath() { return path.join(this.agentDir, 'npm', 'node_modules', 'fixture-installed'); }",
|
||||
'}',
|
||||
].join('\n'), 'utf8');
|
||||
const calls: DevicePackageInstallInput[] = [];
|
||||
const runInstall = vi.fn(async (input: DevicePackageInstallInput) => {
|
||||
calls.push(input);
|
||||
const packageRoot = path.join(input.agentDir, 'npm', 'node_modules', 'pi-web-search');
|
||||
const packageRoot = path.join(input.agentDir, 'npm', 'node_modules', 'fixture-installed');
|
||||
await writeJson(path.join(packageRoot, 'package.json'), {
|
||||
name: 'pi-web-search',
|
||||
version: '2.4.0',
|
||||
@@ -135,7 +147,7 @@ describe('DevicePackageManager', () => {
|
||||
const manager = new DevicePackageManager({
|
||||
rootDir: path.join(root, 'store'),
|
||||
executablePath: 'Makelore.exe',
|
||||
cliPath: 'pi-cli.js',
|
||||
cliPath,
|
||||
npmCliPath: 'npm-cli.js',
|
||||
runInstall,
|
||||
now: () => NOW,
|
||||
|
||||
@@ -11,6 +11,14 @@ import { PiProcessBudget } from '../../electron/coding-runtime/pi/worker-pool';
|
||||
import type { AgentBrowserModule } from '../../electron/agent-browser';
|
||||
import { CodingAttachmentStore } from '../../electron/coding-projects/attachment-store';
|
||||
import { PiProductTools } from '../../electron/coding-runtime/pi/product-tools';
|
||||
import {
|
||||
DevicePackageError,
|
||||
type DevicePackageManager,
|
||||
} from '../../electron/coding-packages/device-package-manager';
|
||||
import {
|
||||
DEVICE_PACKAGE_TOOL_DEFINITIONS,
|
||||
DevicePackageTools,
|
||||
} from '../../electron/coding-packages/device-package-tools';
|
||||
import { CodingCapabilityRegistryImpl } from '../../electron/coding-plugins/registry';
|
||||
import type { PluginPolicyClientState } from '../../electron/services/plugin-policy-client';
|
||||
import {
|
||||
@@ -48,6 +56,79 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe('Makelore Pi extension bundle', () => {
|
||||
it('preserves a closed Device Package failure through the real worker bridge', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-device-package-error-'));
|
||||
roots.push(root);
|
||||
const manager = {
|
||||
async prepare() {
|
||||
throw new DevicePackageError(
|
||||
'local_package_dependency_failed',
|
||||
'Bundled Pi package manager is unavailable',
|
||||
);
|
||||
},
|
||||
} as unknown as DevicePackageManager;
|
||||
const host = new PiManagedExtensionHost();
|
||||
host.configureProductTools(new PiProductTools({
|
||||
browser: {} as AgentBrowserModule,
|
||||
attachments: new CodingAttachmentStore(path.join(root, 'attachments')),
|
||||
bundledSkillsDir: path.resolve('resources/coding-skills'),
|
||||
devicePackageTools: new DevicePackageTools(manager),
|
||||
}));
|
||||
hosts.push(host);
|
||||
const prepareTool = DEVICE_PACKAGE_TOOL_DEFINITIONS.find(
|
||||
({ name }) => name === 'local_package_prepare',
|
||||
);
|
||||
if (!prepareTool) throw new Error('Device Package prepare definition missing');
|
||||
const worker = await host.registerWorker({
|
||||
conversationId: 'device-package-conversation',
|
||||
generation: 1,
|
||||
projectId: 'project-a',
|
||||
projectPath: root,
|
||||
extensionsDir: root,
|
||||
tools: [prepareTool],
|
||||
});
|
||||
await host.bindRun('device-package-conversation', 1, 'device-package-run');
|
||||
const previous = {
|
||||
bridge: process.env.MAKELORE_PI_BRIDGE_URL,
|
||||
token: process.env.MAKELORE_PI_WORKER_TOKEN,
|
||||
context: process.env.MAKELORE_PI_CONTEXT_FILE,
|
||||
role: process.env.MAKELORE_PI_WORKER_ROLE,
|
||||
};
|
||||
Object.assign(process.env, worker.env);
|
||||
try {
|
||||
const module = await import(
|
||||
/* @vite-ignore */ `${pathToFileURL(worker.extensionPath).href}?device-package=${Date.now()}`
|
||||
) as {
|
||||
default(factory: {
|
||||
registerTool(tool: ExtensionTool): void;
|
||||
on(event: string, handler: ExtensionHandler): void;
|
||||
}): void | Promise<void>;
|
||||
};
|
||||
const tools = new Map<string, ExtensionTool>();
|
||||
await module.default({
|
||||
registerTool: (tool) => tools.set(tool.name, tool),
|
||||
on: () => undefined,
|
||||
});
|
||||
|
||||
await expect(tools.get('local_package_prepare')?.execute?.(
|
||||
'prepare-a',
|
||||
{ source: 'npm:pi-web-search' },
|
||||
new AbortController().signal,
|
||||
)).rejects.toThrow(
|
||||
'local_package_dependency_failed: Bundled Pi package manager is unavailable',
|
||||
);
|
||||
} finally {
|
||||
for (const [key, value] of Object.entries(previous)) {
|
||||
const environmentKey = key === 'bridge' ? 'MAKELORE_PI_BRIDGE_URL'
|
||||
: key === 'token' ? 'MAKELORE_PI_WORKER_TOKEN'
|
||||
: key === 'context' ? 'MAKELORE_PI_CONTEXT_FILE'
|
||||
: 'MAKELORE_PI_WORKER_ROLE';
|
||||
if (value === undefined) delete process.env[environmentKey];
|
||||
else process.env[environmentKey] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('hydrates persisted Pi identity through reconnect and event replay into the capability registry', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-persisted-identity-'));
|
||||
roots.push(root);
|
||||
|
||||
@@ -289,7 +289,7 @@ describe('managed Pi worker opener', () => {
|
||||
expect(argv).toContain('grilling');
|
||||
expect(argv).toContain('--session-id');
|
||||
expect(argv).toContain('--extension');
|
||||
expect(argv).toContain('makelore-runtime-v4.mjs');
|
||||
expect(argv).toContain('makelore-runtime-v5.mjs');
|
||||
expect(options.additionalArgs?.filter((argument) => argument === '--extension')).toHaveLength(2);
|
||||
expect(options.additionalArgs).toEqual(expect.arrayContaining([
|
||||
'--skill', deviceSkillPath, '--extension', deviceExtensionPath,
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
verifyBundledCodingPluginResources,
|
||||
defaultProductExecutable,
|
||||
readPackagedMarketplaceTrustSource,
|
||||
verifyPackagedDevicePackageRuntime,
|
||||
verifyPackagedMarketplaceClientArtifact,
|
||||
validatePiArtifactMetadata,
|
||||
verifyMarketplaceClientArtifact,
|
||||
@@ -326,6 +327,41 @@ describe('final Pi product artifact verification', () => {
|
||||
await expect(readPackagedMarketplaceTrustSource(appAsar)).rejects.toThrow('trust source');
|
||||
});
|
||||
|
||||
it('rejects the incomplete app Pi graph and requires the bundled runtime package manager', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-device-package-runtime-asar-'));
|
||||
roots.push(root);
|
||||
const source = path.join(root, 'source');
|
||||
await mkdir(path.join(source, 'dist-electron'), { recursive: true });
|
||||
await writeFile(path.join(source, 'package.json'), JSON.stringify({ main: 'dist-electron/main.js' }));
|
||||
await writeFile(
|
||||
path.join(source, 'dist-electron', 'main.js'),
|
||||
'export async function prepare() { return await import("@earendil-works/pi-coding-agent"); }',
|
||||
);
|
||||
const appAsar = path.join(root, 'app.asar');
|
||||
await createAsarFixture(source, appAsar);
|
||||
const runtimePlatform = {
|
||||
devicePackageManager: { defaultPackageManager: 'function', settingsManager: 'function' },
|
||||
};
|
||||
|
||||
await expect(verifyPackagedDevicePackageRuntime(appAsar, runtimePlatform))
|
||||
.rejects.toThrow('incomplete app graph');
|
||||
|
||||
await writeFile(
|
||||
path.join(source, 'dist-electron', 'main.js'),
|
||||
'export const devicePackageManagerAuthority = "bundled-pi-runtime";',
|
||||
);
|
||||
const cleanAsar = path.join(root, 'clean.asar');
|
||||
await createAsarFixture(source, cleanAsar);
|
||||
await expect(verifyPackagedDevicePackageRuntime(cleanAsar, runtimePlatform)).resolves.toEqual({
|
||||
authority: 'bundled-pi-runtime',
|
||||
appAsarRootImport: false,
|
||||
exports: runtimePlatform.devicePackageManager,
|
||||
result: 'pass',
|
||||
});
|
||||
await expect(verifyPackagedDevicePackageRuntime(cleanAsar, { devicePackageManager: null }))
|
||||
.rejects.toThrow('cannot load the Device Package manager');
|
||||
});
|
||||
|
||||
it('binds Marketplace route, Renderer, and effective markers to the package main graph', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-marketplace-contract-asar-'));
|
||||
roots.push(root);
|
||||
|
||||
Reference in New Issue
Block a user