@lemoncloud/eureka-inject
v1.3.0
Published
Deterministic codemod: AI Studio (Vite+React+TS) build output -> Eureka backend applet. FS-free core + in-memory zip adapter (AWS Lambda ready).
Readme
@lemoncloud/eureka-inject
Deterministic codemod that turns an AI Studio (Vite + React + TS) build export into an
Eureka backend applet. No LLM judgement — pure AST/structural rules. Match failure ⇒ hard error
(no guessing). Idempotent. Non-destructive. index.html is never touched.
The core is FS-free and ships with an in-memory zip adapter, so it runs unchanged inside an AWS Lambda (feed it an S3 object Buffer, write the result Buffer back).
Install
npm i @lemoncloud/eureka-injectProgrammatic API (Lambda / Node)
import { transform, zipBufferToFileMap, fileMapToZipBuffer } from '@lemoncloud/eureka-inject';
// inside a Lambda handler — no disk touched:
const files = zipBufferToFileMap(s3ObjectBuffer); // Buffer -> FileMap
const { files: out, report } = transform(files); // pure codemod
const outZip = fileMapToZipBuffer(out); // FileMap -> Buffertransform(files: FileMap): { files: FileMap; report: Report }— the pure codemod.FileMapisRecord<string, string | Buffer>(path → content). ThrowsInjectErroron any structural mismatch.zipBufferToFileMap(zip: Buffer): FileMap/fileMapToZipBuffer(files: FileMap): Buffer— in-memory zip ↔ FileMap, no filesystem.
ts-morph and adm-zip are runtime dependencies (kept external); bundle them as your Lambda packaging requires.
CLI
eureka-inject --input app.zip [--output app.eureka.zip] [--out-dir ./out]Prints a report of applied / skipped / warnings.
AWS Lambda handler
The codemod is import-and-call: aws-sdk stays in your project. A full S3-in/S3-out handler:
import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { fileMapToZipBuffer, InjectError, transform, zipBufferToFileMap } from '@lemoncloud/eureka-inject';
const s3 = new S3Client({});
export const handler = async (event) => {
const { bucket, key } = event.Records?.[0]
? { bucket: event.Records[0].s3.bucket.name, key: decodeURIComponent(event.Records[0].s3.object.key) }
: { bucket: event.bucket, key: event.key };
const obj = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
const inputZip = Buffer.from(await obj.Body.transformToByteArray());
try {
const { files, report } = transform(zipBufferToFileMap(inputZip)); // pure, in-memory
const outKey = key.replace(/\.zip$/i, '.eureka.zip');
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: outKey, Body: fileMapToZipBuffer(files) }));
return { statusCode: 200, ok: true, outKey, report };
} catch (e) {
if (e instanceof InjectError) return { statusCode: 422, ok: false, error: e.message }; // refused, not crashed
throw e;
}
};- The transform path is FS-free; the only I/O is your S3 get/put.
InjectErrormeans the input isn't a recognized AI Studio shape — refuse (422), don't crash.ts-morph(the TS compiler, ~MB) is pulled in transitively; bundle with esbuild or shipnode_modules— within Lambda's 250MB unzipped limit.
A copy-paste handler.ts + a "test the tarball before publish" recipe live in
examples/lambda-handler.
What it does
Swaps the new GoogleGenAI(...) constructor for createEurekaGenAI() (a WebSocket-backed drop-in),
adds lemon-model, upserts vite defines, wraps the render root in <EurekaProvider>, and injects the
eureka/* runtime. Server-side (Express) Gemini exports are re-wired SSR→SPA. See the repo spec
(eureka-inject-spec.md) for the full rule set.
