nopy: add create-cube command scaffolding a cube from bundled templates
Publish snapshot / snapshot (push) Successful in 1m17s
Publish snapshot / snapshot (push) Successful in 1m17s
Gathers id, name and directory from flags or prompts (only what the flags do not supply), then writes manifest.mjs + deploy.py from templates under src/templates/cube. Templates are named *.example.* so the template directory itself can never match the loader's manifest+deploy pair rule. The scaffold refuses a directory that already holds cube files by the loader's own patterns, checks the id against the loaded cube set (best effort, exempting the target directory so --force re-scaffolds work), and warns when the target lands outside every configured cube directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ApuW1MMGK2pUQ9mTVRTxc5
This commit is contained in:
co-authored by
Claude Fable 5
parent
568d4c83ff
commit
8973ff7113
@@ -41,6 +41,7 @@ import { Cube, Manifest } from '@bitsquare/nopy-cubes';
|
||||
import { Variables } from '../src/nopy.common.js';
|
||||
import {
|
||||
AuthSelection,
|
||||
CubeScaffoldPrompts,
|
||||
CubeSelection,
|
||||
HostSelection,
|
||||
PasswordSelection,
|
||||
@@ -532,3 +533,50 @@ describe('VariableAssignment', () => {
|
||||
expect(variables.get('svc')).toEqual({ port: 9090, enabled: true, maybe: null });
|
||||
});
|
||||
});
|
||||
|
||||
describe('CubeScaffoldPrompts', () => {
|
||||
const suggestDir = vi.fn((id: string) => `cubes/${id}`);
|
||||
|
||||
it('passes fully-given answers through without asking anything', async () => {
|
||||
inquirerPrompt.mockResolvedValue({});
|
||||
|
||||
const given = { id: 'net:x', name: 'X', dir: 'cubes/net/x' };
|
||||
await expect(CubeScaffoldPrompts(given, suggestDir)).resolves.toEqual(given);
|
||||
|
||||
for (const q of questions()) {
|
||||
expect(q.when()).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('asks only for what is missing and merges the answers', async () => {
|
||||
inquirerPrompt.mockResolvedValue({ name: 'Typed name', dir: 'typed/dir' });
|
||||
|
||||
const result = await CubeScaffoldPrompts({ id: 'net:x' }, suggestDir);
|
||||
|
||||
expect(result).toEqual({ id: 'net:x', name: 'Typed name', dir: 'typed/dir' });
|
||||
expect(question('id')?.when()).toBe(false);
|
||||
expect(question('name')?.when()).toBe(true);
|
||||
expect(question('dir')?.when()).toBe(true);
|
||||
});
|
||||
|
||||
it('wires the id validation into the prompt', async () => {
|
||||
inquirerPrompt.mockResolvedValue({ id: 'ok', name: 'n', dir: 'd' });
|
||||
|
||||
await CubeScaffoldPrompts({}, suggestDir);
|
||||
|
||||
const validate = question('id')?.validate;
|
||||
expect(validate('net:x')).toBe(true);
|
||||
expect(validate('has space')).toBeTypeOf('string');
|
||||
expect(question('name')?.validate(' ')).toBeTypeOf('string');
|
||||
});
|
||||
|
||||
it('derives the directory default from the id, typed or given', async () => {
|
||||
inquirerPrompt.mockResolvedValue({ id: 'typed:id', name: 'n', dir: 'd' });
|
||||
|
||||
await CubeScaffoldPrompts({}, suggestDir);
|
||||
expect(question('dir')?.default({ id: 'typed:id' })).toBe('cubes/typed:id');
|
||||
|
||||
await CubeScaffoldPrompts({ id: 'given:id' }, suggestDir);
|
||||
expect(question('dir')?.default({})).toBe('cubes/given:id');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user