[refactor] moving cubes into own package"
Publish snapshot / snapshot (push) Successful in 1m2s

[fix] default parameter run records parameters in session for replay[fix] remove default parameters for several cubes
This commit is contained in:
Benjamin Diedrichsen
2026-07-28 12:18:10 +02:00
parent ac050c4459
commit 6ecb2c366f
130 changed files with 3386 additions and 520 deletions
@@ -0,0 +1,39 @@
/**
* Tests for the manifest factories
*/
import { describe, expect, it } from 'vitest';
import { z } from 'zod';
import { createManifest, manifest } from '../src/factories.js';
describe('createManifest', () => {
it('creates manifest with basic properties', () => {
const m = createManifest({
id: 'test-cube',
name: 'Test Cube',
});
expect(m.id).toBe('test-cube');
expect(m.name).toBe('Test Cube');
expect(m.schema).toBeDefined();
expect(m.before).toEqual([]);
expect(m.after).toEqual([]);
});
it('accepts schema', () => {
const schema = z.object({
VERSION: z.string().default('1.0'),
});
const m = createManifest({
name: 'Test Cube',
schema,
});
expect(m.schema).toBe(schema);
});
it('manifest is alias for createManifest', () => {
expect(manifest).toBe(createManifest);
});
});
@@ -0,0 +1,29 @@
/**
* A schema that behaves like zod's but does not share zod's prototypes.
*
* Once cubes arrive from `node_modules`, the schema a manifest builds may come
* from a *second* copy of zod — its own dependency, or one shipped inside a
* bundle. Such a schema is structurally identical and `instanceof` blind to it.
* Rebuilding the nodes as plain objects reproduces that from inside a single
* process, so anything that reads zod's internals stays pinned to `def.type`.
*/
import type { z } from 'zod';
/** Strips the prototype off a schema node and everything it wraps. */
function strip(node: unknown): unknown {
const def = { ...(node as { def: Record<string, unknown> }).def };
if (def.innerType) def.innerType = strip(def.innerType);
return { def };
}
export function foreignZodSchema<S extends z.ZodObject<any>>(schema: S): S {
return {
// Parsing is not what is under test — delegate it and keep the real
// behaviour, so only the introspection path sees the foreign nodes.
safeParse: (value: unknown) => schema.safeParse(value),
shape: Object.fromEntries(
Object.entries(schema.shape).map(([key, node]) => [key, strip(node)])
),
} as unknown as S;
}
+171
View File
@@ -0,0 +1,171 @@
/**
* Tests for the Cube runtime wrapper: default extraction and the required-key
* check that `--use-defaults` relies on.
*/
import { describe, expect, it } from 'vitest';
import { z } from 'zod';
import { Cube, Manifest } from '../src/types.js';
import { foreignZodSchema } from './helpers/foreign-zod.js';
const cube = (schema: z.ZodObject<any>) =>
new Cube(Manifest.create({ id: 'c', name: 'C', schema }), '/cubes/c', 'deploy.py');
describe('Cube', () => {
it('reads id and name off the manifest', () => {
const c = cube(z.object({}));
expect(c.id).toBe('c');
expect(c.name).toBe('C');
});
it('defaults its source to its own directory', () => {
// What the loader overrides when a cube arrives from a package; a cube
// built by hand still has to answer the question.
expect(cube(z.object({})).source).toEqual({ type: 'dir', dir: '/cubes/c' });
});
it('keeps the source it was constructed with', () => {
const source = { type: 'package' as const, packageName: '@acme/cubes-net', dir: '/pkg/cubes' };
const c = new Cube(
Manifest.create({ id: 'c', name: 'C' }),
'/pkg/cubes/c',
'deploy.py',
source
);
expect(c.source).toBe(source);
});
});
describe('Cube.getDefaults', () => {
it('resolves every default when the whole schema parses', () => {
const c = cube(
z.object({
PORT: z.number().default(8080),
NAME: z.string().default('svc'),
})
);
expect(c.getDefaults()).toEqual({ PORT: 8080, NAME: 'svc' });
});
it('keeps the declared defaults when one field has none', () => {
const c = cube(
z.object({
SSID: z.string(),
PRIORITY: z.number().default(10),
HIDDEN: z.boolean().default(false),
})
);
expect(c.getDefaults()).toEqual({ PRIORITY: 10, HIDDEN: false });
});
it('unwraps a default sitting under optional or nullable', () => {
const c = cube(
z.object({
REQUIRED: z.string(),
A: z.number().default(1).optional(),
B: z.number().default(2).nullable(),
C: z.number().optional().default(3),
})
);
expect(c.getDefaults()).toEqual({ A: 1, B: 2, C: 3 });
});
it('evaluates a lazily declared default', () => {
const c = cube(
z.object({ REQUIRED: z.string(), TOKEN: z.string().default(() => 'generated') })
);
expect(c.getDefaults()).toEqual({ TOKEN: 'generated' });
});
it('omits an optional field that declares no default', () => {
const c = cube(z.object({ REQUIRED: z.string(), MAYBE: z.string().optional() }));
expect(c.getDefaults()).toEqual({});
});
it('returns an empty object for an empty schema', () => {
expect(cube(z.object({})).getDefaults()).toEqual({});
});
it('reads defaults off a schema built by a different copy of zod', () => {
// The per-field fallback reads zod's internals directly. Under `instanceof`
// a foreign schema yields no defaults at all, without erroring.
const c = cube(
foreignZodSchema(
z.object({
REQUIRED: z.string(),
PRIORITY: z.number().default(10),
NESTED: z.number().default(2).optional(),
})
)
);
expect(c.getDefaults()).toEqual({ PRIORITY: 10, NESTED: 2 });
});
});
describe('Cube.requiredKeys', () => {
it('lists the fields with neither a default nor optionality', () => {
const c = cube(
z.object({
SSID: z.string(),
PASSWORD: z.string(),
PRIORITY: z.number().default(10),
NOTE: z.string().optional(),
})
);
expect(c.requiredKeys()).toEqual(['SSID', 'PASSWORD']);
});
it('treats a nullable field without a default as required', () => {
const c = cube(z.object({ MAYBE: z.string().nullable() }));
expect(c.requiredKeys()).toEqual(['MAYBE']);
});
it('is empty when every field can fill itself in', () => {
const c = cube(z.object({ A: z.string().default('a'), B: z.string().optional() }));
expect(c.requiredKeys()).toEqual([]);
});
});
describe('Cube.secrets', () => {
it('is empty when the manifest declares none', () => {
const c = cube(z.object({ PASSWORD: z.string().default('x') }));
expect(c.secrets).toEqual([]);
// No name-based guessing: only what the manifest says.
expect(c.isSecret('PASSWORD')).toBe(false);
});
it('reports what the manifest declared', () => {
const c = new Cube(
Manifest.create({
id: 'c',
name: 'C',
schema: z.object({ USER: z.string(), PASSWORD: z.string() }),
secrets: ['PASSWORD'],
}),
'/cubes/c',
'deploy.py'
);
expect(c.secrets).toEqual(['PASSWORD']);
expect(c.isSecret('PASSWORD')).toBe(true);
expect(c.isSecret('USER')).toBe(false);
});
it('defaults to an empty list on a manifest built by hand', () => {
const c = new Cube({ id: 'c', name: 'C', schema: z.object({}) }, '/cubes/c', 'deploy.py');
expect(c.secrets).toEqual([]);
});
});
+44
View File
@@ -0,0 +1,44 @@
/**
* Tests for the uniqid helper
*/
import { describe, expect, it } from 'vitest';
import { uniqid } from '../src/utils.js';
describe('uniqid', () => {
it('generates string of default length (5)', () => {
const id = uniqid();
expect(id).toHaveLength(5);
});
it('generates string of specified length', () => {
expect(uniqid(10)).toHaveLength(10);
expect(uniqid(3)).toHaveLength(3);
expect(uniqid(20)).toHaveLength(20);
});
it('generates alphanumeric characters only', () => {
const id = uniqid(100);
expect(id).toMatch(/^[A-Za-z0-9]+$/);
});
it('generates different values on subsequent calls', () => {
const ids = new Set<string>();
for (let i = 0; i < 100; i++) {
ids.add(uniqid(10));
}
// Should have many unique values (some collisions possible but unlikely)
expect(ids.size).toBeGreaterThan(90);
});
it('handles edge case of length 1', () => {
const id = uniqid(1);
expect(id).toHaveLength(1);
expect(id).toMatch(/^[A-Za-z0-9]$/);
});
it('handles edge case of length 0', () => {
const id = uniqid(0);
expect(id).toBe('');
});
});