feat: add Pi process and RPC foundation
This commit is contained in:
@@ -8,13 +8,16 @@ import {
|
||||
parseProbeArgs,
|
||||
resolvePiRuntime,
|
||||
runProbe,
|
||||
stagePiRuntime,
|
||||
validatePiIdentity,
|
||||
validateStagedClosure,
|
||||
} from './probe-pi-runtime.mjs';
|
||||
import {
|
||||
PI_RUNTIME_MANIFEST,
|
||||
defaultPiBundleTarget,
|
||||
stagePiRuntimeBundle,
|
||||
} from './lib/pi-runtime-bundle.mjs';
|
||||
|
||||
const PRODUCT_NAME = 'MakelorePiProbe';
|
||||
const ARTIFACT_LABEL = 'controlled-electron-builder-dir-app-asar';
|
||||
const ARTIFACT_LABEL = 'controlled-electron-builder-dir-extra-resources';
|
||||
|
||||
async function pathExists(path) {
|
||||
try {
|
||||
@@ -92,9 +95,12 @@ async function inspectPackagedClosure(executable, resourcesDirectory, assets) {
|
||||
const path = require('node:path');
|
||||
const resources = process.env.PI_PROBE_RESOURCES_DIRECTORY;
|
||||
if (!resources) throw new Error('PI_PROBE_RESOURCES_DIRECTORY is required');
|
||||
const root = path.join(resources, 'app.asar');
|
||||
const unpackedRoot = path.join(resources, 'app.asar.unpacked');
|
||||
const root = path.join(resources, 'pi-runtime');
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
||||
const runtimeManifestPath = path.join(root, '${PI_RUNTIME_MANIFEST}');
|
||||
const runtimeManifest = fs.existsSync(runtimeManifestPath)
|
||||
? JSON.parse(fs.readFileSync(runtimeManifestPath, 'utf8'))
|
||||
: null;
|
||||
const shrinkwrap = JSON.parse(fs.readFileSync(path.join(root, 'npm-shrinkwrap.json'), 'utf8'));
|
||||
const assets = JSON.parse(process.env.PI_PROBE_ASSETS_JSON);
|
||||
const libc = process.platform === 'linux'
|
||||
@@ -140,12 +146,11 @@ async function inspectPackagedClosure(executable, resourcesDirectory, assets) {
|
||||
missingPackages.push(packagePath);
|
||||
}
|
||||
const missingAssets = assets.filter((asset) => !fs.existsSync(path.join(root, ...asset.split('/'))));
|
||||
const missingUnpackedNativeAssets = assets
|
||||
.filter((asset) => asset.endsWith('.node'))
|
||||
.filter((asset) => !fs.existsSync(path.join(unpackedRoot, ...asset.split('/'))));
|
||||
process.stdout.write(JSON.stringify({
|
||||
packageName: packageJson.name,
|
||||
packageVersion: packageJson.version,
|
||||
runtimeManifestExists: Boolean(runtimeManifest),
|
||||
runtimeManifestTarget: runtimeManifest?.target ?? null,
|
||||
cliExists: fs.existsSync(path.join(root, 'dist', 'cli.js')),
|
||||
lockedPackages: Math.max(0, Object.keys(shrinkwrap.packages || {}).length - 1),
|
||||
expectedPackages,
|
||||
@@ -154,7 +159,7 @@ async function inspectPackagedClosure(executable, resourcesDirectory, assets) {
|
||||
relocatedPackages,
|
||||
assetCount: assets.length,
|
||||
missingAssets,
|
||||
missingUnpackedNativeAssets,
|
||||
nativeAssetsOutsideAsar: assets.filter((asset) => asset.endsWith('.node')).length,
|
||||
}));
|
||||
`;
|
||||
const { stdout } = await runCommand(executable, ['-e', script], {
|
||||
@@ -168,9 +173,9 @@ async function inspectPackagedClosure(executable, resourcesDirectory, assets) {
|
||||
const result = JSON.parse(stdout.trim());
|
||||
if (
|
||||
!result.cliExists
|
||||
|| !result.runtimeManifestExists
|
||||
|| result.missingPackages.length > 0
|
||||
|| result.missingAssets.length > 0
|
||||
|| result.missingUnpackedNativeAssets.length > 0
|
||||
) {
|
||||
throw new Error(`Packaged Pi closure is incomplete: ${JSON.stringify(result)}`);
|
||||
}
|
||||
@@ -187,14 +192,20 @@ function assertControlledOutput(projectRoot, outputDirectory) {
|
||||
}
|
||||
}
|
||||
|
||||
async function buildControlledArtifact(projectRoot, appDirectory, outputDirectory) {
|
||||
async function buildControlledArtifact(projectRoot, appDirectory, runtimeDirectory, outputDirectory) {
|
||||
const electronPackage = JSON.parse(await readFile(join(projectRoot, 'node_modules', 'electron', 'package.json'), 'utf8'));
|
||||
const { platformTarget, architecture } = currentTarget();
|
||||
const buildResources = join(appDirectory, 'build-resources');
|
||||
await mkdir(buildResources);
|
||||
await mkdir(buildResources, { recursive: true });
|
||||
await writeFile(join(appDirectory, 'package.json'), `${JSON.stringify({
|
||||
name: 'makelore-pi-production-shape-probe',
|
||||
version: '1.0.0',
|
||||
main: 'main.js',
|
||||
}, null, 2)}\n`);
|
||||
await writeFile(join(appDirectory, 'main.js'), 'process.exit(0);\n');
|
||||
const builderArtifacts = await build({
|
||||
// Use the staged Pi root as the project root so this controlled probe does
|
||||
// not inherit Makelore's production hooks or unrelated extraResources.
|
||||
// Keep the probe application minimal while copying the permanent stage to
|
||||
// the same resources/pi-runtime location used by the product configuration.
|
||||
projectDir: appDirectory,
|
||||
targets: platformTarget.createTarget('dir', architecture),
|
||||
publish: 'never',
|
||||
@@ -208,13 +219,22 @@ async function buildControlledArtifact(projectRoot, appDirectory, outputDirector
|
||||
buildResources,
|
||||
},
|
||||
files: [
|
||||
'dist/**/*',
|
||||
'main.js',
|
||||
'package.json',
|
||||
'npm-shrinkwrap.json',
|
||||
'node_modules/**/*',
|
||||
],
|
||||
extraResources: [
|
||||
{
|
||||
from: runtimeDirectory,
|
||||
to: 'pi-runtime',
|
||||
filter: ['**/*', '!node_modules{,/**/*}'],
|
||||
},
|
||||
{
|
||||
from: join(runtimeDirectory, 'node_modules'),
|
||||
to: 'pi-runtime/node_modules',
|
||||
filter: ['**/*'],
|
||||
},
|
||||
],
|
||||
asar: true,
|
||||
asarUnpack: ['**/*.node'],
|
||||
npmRebuild: false,
|
||||
win: {
|
||||
executableName: PRODUCT_NAME,
|
||||
@@ -239,22 +259,37 @@ export async function runPackagedProbe(options, projectRoot = process.cwd()) {
|
||||
assertControlledOutput(resolvedProjectRoot, outputDirectory);
|
||||
const scratchRoot = await mkdtemp(join(tmpdir(), 'makelore-pi-packaged-probe-'));
|
||||
const appDirectory = join(scratchRoot, 'app');
|
||||
const runtimeDirectory = join(scratchRoot, 'runtime');
|
||||
try {
|
||||
const sourceRuntime = await resolvePiRuntime(resolvedProjectRoot);
|
||||
const identity = await validatePiIdentity(sourceRuntime);
|
||||
await stagePiRuntime(sourceRuntime, appDirectory);
|
||||
const stagedClosure = await validateStagedClosure(appDirectory);
|
||||
const runtimeManifest = await stagePiRuntimeBundle({
|
||||
projectRoot: resolvedProjectRoot,
|
||||
sourcePackageRoot: sourceRuntime.packageRoot,
|
||||
destination: runtimeDirectory,
|
||||
target: defaultPiBundleTarget(),
|
||||
});
|
||||
const stagedClosure = {
|
||||
lockfileVersion: runtimeManifest.lockfileVersion,
|
||||
expectedPackages: runtimeManifest.productionPackages.length,
|
||||
platformSkippedPackages: runtimeManifest.platformSkippedPackages,
|
||||
assets: runtimeManifest.runtimeAssets,
|
||||
omittedPublishedDevDependencies: runtimeManifest.staging.omittedPublishedDevDependencies,
|
||||
manifest: runtimeManifest,
|
||||
};
|
||||
await rm(outputDirectory, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
||||
await mkdir(dirname(outputDirectory), { recursive: true });
|
||||
const builderArtifacts = await buildControlledArtifact(
|
||||
resolvedProjectRoot,
|
||||
appDirectory,
|
||||
runtimeDirectory,
|
||||
outputDirectory,
|
||||
);
|
||||
const executable = await findPackagedExecutable(outputDirectory);
|
||||
const resourcesDirectory = packagedResourcesDirectory(executable);
|
||||
const appAsar = join(resourcesDirectory, 'app.asar');
|
||||
const cliPath = join(appAsar, 'dist', 'cli.js');
|
||||
const packagedRuntime = join(resourcesDirectory, 'pi-runtime');
|
||||
const cliPath = join(packagedRuntime, 'dist', 'cli.js');
|
||||
if (!await pathExists(appAsar)) throw new Error(`Packaged app.asar is missing: ${appAsar}`);
|
||||
const packagedClosure = await inspectPackagedClosure(
|
||||
executable,
|
||||
@@ -279,7 +314,7 @@ export async function runPackagedProbe(options, projectRoot = process.cwd()) {
|
||||
outputDirectory,
|
||||
executable,
|
||||
appAsar,
|
||||
appAsarUnpacked: join(resourcesDirectory, 'app.asar.unpacked'),
|
||||
runtimeRoot: packagedRuntime,
|
||||
builderArtifacts,
|
||||
},
|
||||
stagedClosure,
|
||||
@@ -317,6 +352,6 @@ const isMain = process.argv[1]
|
||||
if (isMain) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`${error.stack ?? error.message}\n`);
|
||||
process.exitCode = 1;
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user