bank20baht-cli 0.0.2 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/application/create.js +7 -1
- package/dist/application/generate.js +31 -1
- package/dist/cli.js +52 -9
- package/dist/domain/create-plan.js +1 -1
- package/dist/domain/form-dsl.js +21 -6
- package/dist/infrastructure/fs-reader.js +7 -2
- package/dist/infrastructure/fs-writer.js +10 -3
- package/dist/infrastructure/shell-runner.js +5 -1
- package/dist/infrastructure/template-renderer.js +16 -0
- package/dist/infrastructure/template-source.js +4 -1
- package/package.json +5 -3
- package/templates/create/apps/web/src/App.tsx.hbs +1 -1
- package/dist/application/create.d.ts +0 -16
- package/dist/application/generate.d.ts +0 -33
- package/dist/application/ports.d.ts +0 -23
- package/dist/cli.d.ts +0 -2
- package/dist/domain/blueprint.d.ts +0 -23
- package/dist/domain/create-plan.d.ts +0 -24
- package/dist/domain/form-dsl.d.ts +0 -20
- package/dist/domain/generate-plan.d.ts +0 -23
- package/dist/domain/naming.d.ts +0 -2
- package/dist/infrastructure/fs-reader.d.ts +0 -5
- package/dist/infrastructure/fs-writer.d.ts +0 -6
- package/dist/infrastructure/hbs-renderer.d.ts +0 -4
- package/dist/infrastructure/hbs-renderer.js +0 -7
- package/dist/infrastructure/shell-runner.d.ts +0 -4
- package/dist/infrastructure/template-source.d.ts +0 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nattapong Promthong
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -21,7 +21,13 @@ export function runCreate(input, deps) {
|
|
|
21
21
|
deps.log.step(`${blueprint.files.length} files written to ${blueprint.root}`);
|
|
22
22
|
for (const c of blueprint.postCommands) {
|
|
23
23
|
deps.log.step(c.label);
|
|
24
|
-
|
|
24
|
+
try {
|
|
25
|
+
deps.runner.run(c.cmd, c.args, blueprint.root);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
const retry = `${c.cmd} ${c.args.join(' ')}`;
|
|
29
|
+
throw new CreateError(`${err instanceof Error ? err.message : String(err)}\nproject files are written — cd ${input.name} and rerun: ${retry}`);
|
|
30
|
+
}
|
|
25
31
|
}
|
|
26
32
|
deps.log.info('');
|
|
27
33
|
deps.log.info(`Done. Next:`);
|
|
@@ -2,17 +2,47 @@ import { buildFormDefinition, FormDslError } from '../domain/form-dsl.js';
|
|
|
2
2
|
import { DEFAULT_CONFIG, modelDir, planForm, planInit, planModel, validateDomainName, } from '../domain/generate-plan.js';
|
|
3
3
|
export class GenerateError extends Error {
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* bahtui.json path-ish fields feed directly into fs paths (modelDir(), eden
|
|
7
|
+
* client wiring). A cloned repo's bahtui.json is untrusted input — reject
|
|
8
|
+
* anything that could escape the project root before it ever reaches a path
|
|
9
|
+
* join (defense in depth alongside DiskFileWriter's own root confinement).
|
|
10
|
+
*/
|
|
11
|
+
const SAFE_RELATIVE_PATH = /^[A-Za-z0-9._/-]+$/;
|
|
12
|
+
function validateConfigPath(field, value) {
|
|
13
|
+
if (!SAFE_RELATIVE_PATH.test(value)) {
|
|
14
|
+
throw new GenerateError(`bahtui.json "${field}" contains invalid characters: ${JSON.stringify(value)}.`);
|
|
15
|
+
}
|
|
16
|
+
if (value.startsWith('/')) {
|
|
17
|
+
throw new GenerateError(`bahtui.json "${field}" must be a relative path: ${JSON.stringify(value)}.`);
|
|
18
|
+
}
|
|
19
|
+
if (value.split('/').includes('..')) {
|
|
20
|
+
throw new GenerateError(`bahtui.json "${field}" must not contain ".." segments: ${JSON.stringify(value)}.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function isPlainObject(value) {
|
|
24
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
5
26
|
function loadConfig(root, reader) {
|
|
6
27
|
const raw = reader.read(`${root}/bahtui.json`);
|
|
7
28
|
if (raw === null) {
|
|
8
29
|
throw new GenerateError('No bahtui.json here — run "bahtui init" (or create) first.');
|
|
9
30
|
}
|
|
31
|
+
let parsed;
|
|
10
32
|
try {
|
|
11
|
-
|
|
33
|
+
parsed = JSON.parse(raw);
|
|
12
34
|
}
|
|
13
35
|
catch {
|
|
14
36
|
throw new GenerateError('bahtui.json is not valid JSON.');
|
|
15
37
|
}
|
|
38
|
+
if (!isPlainObject(parsed)) {
|
|
39
|
+
throw new GenerateError('bahtui.json must contain a JSON object.');
|
|
40
|
+
}
|
|
41
|
+
const config = { ...DEFAULT_CONFIG, ...parsed };
|
|
42
|
+
validateConfigPath('libsDir', config.libsDir);
|
|
43
|
+
if (config.edenClient !== undefined)
|
|
44
|
+
validateConfigPath('edenClient', config.edenClient);
|
|
45
|
+
return config;
|
|
16
46
|
}
|
|
17
47
|
function emit(blueprint, dryRun, deps) {
|
|
18
48
|
if (dryRun) {
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { runCreate, CreateError } from './application/create.js';
|
|
6
6
|
import { runGenerate, runInit, GenerateError } from './application/generate.js';
|
|
7
7
|
import { DiskTemplateSource } from './infrastructure/template-source.js';
|
|
8
|
-
import {
|
|
8
|
+
import { TemplateRenderer } from './infrastructure/template-renderer.js';
|
|
9
9
|
import { DiskFileWriter } from './infrastructure/fs-writer.js';
|
|
10
10
|
import { DiskFileReader } from './infrastructure/fs-reader.js';
|
|
11
11
|
import { ShellRunner } from './infrastructure/shell-runner.js';
|
|
@@ -40,13 +40,40 @@ const log = {
|
|
|
40
40
|
step: (m) => console.log(`• ${m}`),
|
|
41
41
|
error: (m) => console.error(`✖ ${m}`),
|
|
42
42
|
};
|
|
43
|
-
/**
|
|
43
|
+
/** Boolean flags recognized anywhere on the command line (no value). */
|
|
44
|
+
const KNOWN_FLAGS = new Set([
|
|
45
|
+
'-v',
|
|
46
|
+
'--version',
|
|
47
|
+
'-h',
|
|
48
|
+
'--help',
|
|
49
|
+
'--dry-run',
|
|
50
|
+
'--force',
|
|
51
|
+
'--no-install',
|
|
52
|
+
'--no-git',
|
|
53
|
+
]);
|
|
54
|
+
/**
|
|
55
|
+
* Value-carrying flags pulled out before positional parsing. Supports both
|
|
56
|
+
* `--flag value` and `--flag=value`. If the token after `--flag value` looks
|
|
57
|
+
* like another flag (starts with `-`), the option is treated as missing
|
|
58
|
+
* rather than swallowing that flag as the value.
|
|
59
|
+
*/
|
|
44
60
|
function takeOption(argv, flag) {
|
|
61
|
+
const eqPrefix = `${flag}=`;
|
|
62
|
+
const eqIndex = argv.findIndex((a) => a.startsWith(eqPrefix));
|
|
63
|
+
if (eqIndex !== -1) {
|
|
64
|
+
const value = argv[eqIndex].slice(eqPrefix.length);
|
|
65
|
+
argv.splice(eqIndex, 1);
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
45
68
|
const i = argv.indexOf(flag);
|
|
46
69
|
if (i === -1)
|
|
47
70
|
return undefined;
|
|
48
71
|
const value = argv[i + 1];
|
|
49
|
-
|
|
72
|
+
if (value === undefined || value.startsWith('-')) {
|
|
73
|
+
argv.splice(i, 1);
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
argv.splice(i, 2);
|
|
50
77
|
return value;
|
|
51
78
|
}
|
|
52
79
|
function main(rawArgv) {
|
|
@@ -57,13 +84,29 @@ function main(rawArgv) {
|
|
|
57
84
|
const args = argv.filter((a) => !a.startsWith('-'));
|
|
58
85
|
const flags = new Set(argv.filter((a) => a.startsWith('-')));
|
|
59
86
|
const [command, name, sub] = args;
|
|
87
|
+
const unknownFlags = [...flags].filter((f) => !KNOWN_FLAGS.has(f));
|
|
88
|
+
if (unknownFlags.length > 0) {
|
|
89
|
+
log.error(`Unknown flag${unknownFlags.length > 1 ? 's' : ''}: ${unknownFlags.join(', ')}`);
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
60
92
|
if (flags.has('-v') || flags.has('--version')) {
|
|
61
|
-
|
|
62
|
-
|
|
93
|
+
try {
|
|
94
|
+
log.info(version());
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
log.error(`cannot read package version: ${String(err)}`);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
63
101
|
}
|
|
64
|
-
if (
|
|
102
|
+
if (flags.has('-h') || flags.has('--help')) {
|
|
65
103
|
log.info(HELP);
|
|
66
|
-
return
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
if (command === undefined) {
|
|
107
|
+
log.error('No command given — see usage below.');
|
|
108
|
+
log.error(HELP);
|
|
109
|
+
return 1;
|
|
67
110
|
}
|
|
68
111
|
const dryRun = flags.has('--dry-run');
|
|
69
112
|
const force = flags.has('--force');
|
|
@@ -87,7 +130,7 @@ function main(rawArgv) {
|
|
|
87
130
|
force,
|
|
88
131
|
}, {
|
|
89
132
|
templates: new DiskTemplateSource(),
|
|
90
|
-
renderer: new
|
|
133
|
+
renderer: new TemplateRenderer(),
|
|
91
134
|
writer: new DiskFileWriter(),
|
|
92
135
|
runner: new ShellRunner(),
|
|
93
136
|
log,
|
|
@@ -117,7 +160,7 @@ function main(rawArgv) {
|
|
|
117
160
|
return 0;
|
|
118
161
|
}
|
|
119
162
|
log.error(`Unknown command "${command}".`);
|
|
120
|
-
log.
|
|
163
|
+
log.error(HELP);
|
|
121
164
|
return 1;
|
|
122
165
|
}
|
|
123
166
|
catch (err) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Template naming conventions (the whole contract between templates/ and here):
|
|
3
3
|
* - `*.hbs` → rendered with the data below, suffix stripped.
|
|
4
4
|
* anything else → copied verbatim (so files with `${{ }}` like ci.yml
|
|
5
|
-
* never meet
|
|
5
|
+
* never meet the renderer).
|
|
6
6
|
* - leading `_` on a basename → `.` (npm pack silently drops/renames real
|
|
7
7
|
* .gitignore files inside packages, so templates ship `_gitignore`).
|
|
8
8
|
*/
|
package/dist/domain/form-dsl.js
CHANGED
|
@@ -28,6 +28,14 @@ const titleCase = (key) => key
|
|
|
28
28
|
.split('_')
|
|
29
29
|
.map((w) => (w ? w[0].toUpperCase() + w.slice(1) : w))
|
|
30
30
|
.join(' ');
|
|
31
|
+
/** `min=`/`max=` must be a real, non-empty number — never silently coerced to NaN/0. */
|
|
32
|
+
function parseNumberMod(name, raw, part) {
|
|
33
|
+
const n = Number(raw);
|
|
34
|
+
if (raw.trim() === '' || !Number.isFinite(n)) {
|
|
35
|
+
throw new FormDslError(`Bad ${name} value ${JSON.stringify(raw)} in "${part}" — expected a number.`);
|
|
36
|
+
}
|
|
37
|
+
return n;
|
|
38
|
+
}
|
|
31
39
|
export function parseFieldsDsl(dsl) {
|
|
32
40
|
const fields = [];
|
|
33
41
|
const parts = dsl
|
|
@@ -49,16 +57,23 @@ export function parseFieldsDsl(dsl) {
|
|
|
49
57
|
if (mod === 'required')
|
|
50
58
|
field.required = true;
|
|
51
59
|
else if (mod.startsWith('options=')) {
|
|
52
|
-
|
|
60
|
+
const opts = mod
|
|
53
61
|
.slice('options='.length)
|
|
54
62
|
.split('|')
|
|
55
|
-
.
|
|
56
|
-
.
|
|
63
|
+
.map((o) => o.trim())
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
if (opts.length === 0) {
|
|
66
|
+
throw new FormDslError(`options modifier needs at least one value in "${part}".`);
|
|
67
|
+
}
|
|
68
|
+
if (new Set(opts).size !== opts.length) {
|
|
69
|
+
throw new FormDslError(`Duplicate option keys in "${part}".`);
|
|
70
|
+
}
|
|
71
|
+
field.options = opts.map((o) => ({ key: o, label: titleCase(o) }));
|
|
57
72
|
}
|
|
58
73
|
else if (mod.startsWith('min='))
|
|
59
|
-
field.min =
|
|
74
|
+
field.min = parseNumberMod('min', mod.slice(4), part);
|
|
60
75
|
else if (mod.startsWith('max='))
|
|
61
|
-
field.max =
|
|
76
|
+
field.max = parseNumberMod('max', mod.slice(4), part);
|
|
62
77
|
else if (mod.startsWith('flag='))
|
|
63
78
|
field.flag = mod.slice(5);
|
|
64
79
|
else
|
|
@@ -83,7 +98,7 @@ export function buildFormDefinition(domain, name, dsl) {
|
|
|
83
98
|
return parseSchema(JSON.stringify(form));
|
|
84
99
|
}
|
|
85
100
|
catch (err) {
|
|
86
|
-
throw new FormDslError(`Engine rejected the form spec: ${err.message}`);
|
|
101
|
+
throw new FormDslError(`Engine rejected the form spec: ${err instanceof Error ? err.message : String(err)}`);
|
|
87
102
|
}
|
|
88
103
|
}
|
|
89
104
|
export class FormDslError extends Error {
|
|
@@ -7,8 +7,13 @@ export class DiskFileReader {
|
|
|
7
7
|
try {
|
|
8
8
|
return readFileSync(path, 'utf8');
|
|
9
9
|
}
|
|
10
|
-
catch {
|
|
11
|
-
|
|
10
|
+
catch (err) {
|
|
11
|
+
// null strictly means "not there" — callers use it to decide the file is
|
|
12
|
+
// absent (e.g. "run bahtui init first"). A permission or IO error must
|
|
13
|
+
// surface, not masquerade as a missing file.
|
|
14
|
+
if (err.code === 'ENOENT')
|
|
15
|
+
return null;
|
|
16
|
+
throw err;
|
|
12
17
|
}
|
|
13
18
|
}
|
|
14
19
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname,
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve, sep } from 'node:path';
|
|
3
3
|
export class DiskFileWriter {
|
|
4
4
|
ensureUsableDir(dir, force) {
|
|
5
5
|
if (!existsSync(dir))
|
|
6
6
|
return;
|
|
7
|
+
if (!statSync(dir).isDirectory()) {
|
|
8
|
+
throw new Error(`${dir} exists and is not a directory.`);
|
|
9
|
+
}
|
|
7
10
|
if (readdirSync(dir).length === 0)
|
|
8
11
|
return;
|
|
9
12
|
if (force)
|
|
@@ -11,8 +14,12 @@ export class DiskFileWriter {
|
|
|
11
14
|
throw new Error(`${dir} already exists and is not empty (use --force to write anyway).`);
|
|
12
15
|
}
|
|
13
16
|
writeFiles(blueprint) {
|
|
17
|
+
const root = resolve(blueprint.root);
|
|
14
18
|
for (const file of blueprint.files) {
|
|
15
|
-
const abs =
|
|
19
|
+
const abs = resolve(root, file.path);
|
|
20
|
+
if (abs !== root && !abs.startsWith(root + sep)) {
|
|
21
|
+
throw new Error(`Refusing to write "${file.path}" — it resolves outside the target directory ${root}.`);
|
|
22
|
+
}
|
|
16
23
|
mkdirSync(dirname(abs), { recursive: true });
|
|
17
24
|
writeFileSync(abs, file.contents);
|
|
18
25
|
}
|
|
@@ -2,8 +2,12 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
export class ShellRunner {
|
|
3
3
|
run(cmd, args, cwd) {
|
|
4
4
|
const result = spawnSync(cmd, args, { cwd, stdio: 'inherit' });
|
|
5
|
-
if (result.error)
|
|
5
|
+
if (result.error) {
|
|
6
|
+
if (result.error.code === 'ENOENT') {
|
|
7
|
+
throw new Error(`'${cmd}' not found on PATH — install it first.`);
|
|
8
|
+
}
|
|
6
9
|
throw result.error;
|
|
10
|
+
}
|
|
7
11
|
if (result.status !== 0) {
|
|
8
12
|
throw new Error(`${cmd} ${args.join(' ')} exited with code ${result.status}`);
|
|
9
13
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The create template set's only expression form is `{{key}}` substitution —
|
|
3
|
+
* a full template engine (handlebars, ~1.5 MB installed) buys nothing here.
|
|
4
|
+
* Unknown variables throw, matching the old handlebars strict-mode behavior.
|
|
5
|
+
* No escaping: these are code/config files, not HTML — quotes must survive.
|
|
6
|
+
*/
|
|
7
|
+
export class TemplateRenderer {
|
|
8
|
+
render(raw, data) {
|
|
9
|
+
return raw.replaceAll(/\{\{\s*([\w.]+)\s*\}\}/g, (_, key) => {
|
|
10
|
+
if (!Object.hasOwn(data, key)) {
|
|
11
|
+
throw new Error(`unknown template variable "${key}"`);
|
|
12
|
+
}
|
|
13
|
+
return String(data[key]);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readdirSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join, relative, sep } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
/** templates/ sits at the package root, next to dist/ — hence ../../ from here. */
|
|
@@ -9,6 +9,9 @@ export class DiskTemplateSource {
|
|
|
9
9
|
}
|
|
10
10
|
load(set) {
|
|
11
11
|
const setRoot = join(this.root, set);
|
|
12
|
+
if (!existsSync(setRoot)) {
|
|
13
|
+
throw new Error(`template set '${set}' missing — reinstall bank20baht-cli`);
|
|
14
|
+
}
|
|
12
15
|
const entries = readdirSync(setRoot, { recursive: true, withFileTypes: true });
|
|
13
16
|
const files = [];
|
|
14
17
|
for (const entry of entries) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bank20baht-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Scaffold bun-workspace DDD monorepos (React + Elysia + Eden) and bahtui generators.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20.12"
|
|
32
|
+
},
|
|
30
33
|
"bin": {
|
|
31
34
|
"bahtui": "./dist/cli.js"
|
|
32
35
|
},
|
|
@@ -40,8 +43,7 @@
|
|
|
40
43
|
"test:watch": "vitest"
|
|
41
44
|
},
|
|
42
45
|
"dependencies": {
|
|
43
|
-
"
|
|
44
|
-
"bank20baht-validator": "^0.0.5"
|
|
46
|
+
"bank20baht-validator": "^0.1.0"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@types/node": "^22",
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { type CreateOptions } from '../domain/create-plan.js';
|
|
2
|
-
import type { CommandRunner, FileWriter, Logger, Renderer, TemplateSource } from './ports.js';
|
|
3
|
-
export interface CreateDeps {
|
|
4
|
-
templates: TemplateSource;
|
|
5
|
-
renderer: Renderer;
|
|
6
|
-
writer: FileWriter;
|
|
7
|
-
runner: CommandRunner;
|
|
8
|
-
log: Logger;
|
|
9
|
-
}
|
|
10
|
-
export interface CreateInput extends CreateOptions {
|
|
11
|
-
dryRun: boolean;
|
|
12
|
-
force: boolean;
|
|
13
|
-
}
|
|
14
|
-
export declare function runCreate(input: CreateInput, deps: CreateDeps): void;
|
|
15
|
-
export declare class CreateError extends Error {
|
|
16
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { FileWriter, Logger } from './ports.js';
|
|
2
|
-
/** Read-only fs the generators need: existence checks + bahtui.json. */
|
|
3
|
-
export interface FileReader {
|
|
4
|
-
exists(path: string): boolean;
|
|
5
|
-
read(path: string): string | null;
|
|
6
|
-
}
|
|
7
|
-
export interface GenerateDeps {
|
|
8
|
-
reader: FileReader;
|
|
9
|
-
writer: FileWriter;
|
|
10
|
-
log: Logger;
|
|
11
|
-
}
|
|
12
|
-
export declare class GenerateError extends Error {
|
|
13
|
-
}
|
|
14
|
-
export interface InitInput {
|
|
15
|
-
root: string;
|
|
16
|
-
frontend: 'react' | null;
|
|
17
|
-
backend: 'elysia' | null;
|
|
18
|
-
dryRun: boolean;
|
|
19
|
-
force: boolean;
|
|
20
|
-
}
|
|
21
|
-
/** `bahtui init` (PRD P2): bahtui.json for an existing repo, paths detected. */
|
|
22
|
-
export declare function runInit(input: InitInput, deps: GenerateDeps): void;
|
|
23
|
-
export interface GenerateInput {
|
|
24
|
-
root: string;
|
|
25
|
-
kind: 'model' | 'form';
|
|
26
|
-
/** `<domain>` for model, `<domain>/<name>` for form. */
|
|
27
|
-
target: string;
|
|
28
|
-
fields?: string;
|
|
29
|
-
dryRun: boolean;
|
|
30
|
-
force: boolean;
|
|
31
|
-
}
|
|
32
|
-
/** `bahtui g model|form` (PRD P3). Engine-validates before any write. */
|
|
33
|
-
export declare function runGenerate(input: GenerateInput, deps: GenerateDeps): void;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { Blueprint } from '../domain/blueprint.js';
|
|
2
|
-
import type { TemplateFile } from '../domain/create-plan.js';
|
|
3
|
-
/** Loads a template set (e.g. "create") shipped inside the CLI package. */
|
|
4
|
-
export interface TemplateSource {
|
|
5
|
-
load(set: string): TemplateFile[];
|
|
6
|
-
}
|
|
7
|
-
export interface Renderer {
|
|
8
|
-
render(raw: string, data: Record<string, unknown>): string;
|
|
9
|
-
}
|
|
10
|
-
export interface FileWriter {
|
|
11
|
-
/** Throws unless `dir` is missing/empty (or `force` is set). */
|
|
12
|
-
ensureUsableDir(dir: string, force: boolean): void;
|
|
13
|
-
writeFiles(blueprint: Blueprint): void;
|
|
14
|
-
}
|
|
15
|
-
export interface CommandRunner {
|
|
16
|
-
/** Runs to completion with inherited stdio; throws on non-zero exit. */
|
|
17
|
-
run(cmd: string, args: string[], cwd: string): void;
|
|
18
|
-
}
|
|
19
|
-
export interface Logger {
|
|
20
|
-
info(message: string): void;
|
|
21
|
-
step(message: string): void;
|
|
22
|
-
error(message: string): void;
|
|
23
|
-
}
|
package/dist/cli.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A Blueprint is WHAT will be generated, as plain data — no fs, no side
|
|
3
|
-
* effects. Adapters (fs-writer, shell-runner) make it real; --dry-run just
|
|
4
|
-
* prints it. This split is the testing seam for every generator.
|
|
5
|
-
*/
|
|
6
|
-
export interface FileSpec {
|
|
7
|
-
/** Path relative to the blueprint root. */
|
|
8
|
-
path: string;
|
|
9
|
-
contents: string;
|
|
10
|
-
}
|
|
11
|
-
export interface PostCommand {
|
|
12
|
-
cmd: string;
|
|
13
|
-
args: string[];
|
|
14
|
-
/** Human-readable step label for progress output. */
|
|
15
|
-
label: string;
|
|
16
|
-
}
|
|
17
|
-
export interface Blueprint {
|
|
18
|
-
/** Absolute directory every FileSpec.path is relative to. */
|
|
19
|
-
root: string;
|
|
20
|
-
files: FileSpec[];
|
|
21
|
-
/** Commands to run inside `root`, in order, after files are written. */
|
|
22
|
-
postCommands: PostCommand[];
|
|
23
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { Blueprint } from './blueprint.js';
|
|
2
|
-
export interface CreateOptions {
|
|
3
|
-
name: string;
|
|
4
|
-
/** Absolute path of the directory to create the project in. */
|
|
5
|
-
targetDir: string;
|
|
6
|
-
install: boolean;
|
|
7
|
-
git: boolean;
|
|
8
|
-
}
|
|
9
|
-
/** A template file as loaded from disk by the infrastructure layer. */
|
|
10
|
-
export interface TemplateFile {
|
|
11
|
-
/** Path relative to the template set root, e.g. "apps/api/src/index.ts.hbs". */
|
|
12
|
-
relPath: string;
|
|
13
|
-
raw: string;
|
|
14
|
-
}
|
|
15
|
-
export type RenderFn = (raw: string, data: Record<string, unknown>) => string;
|
|
16
|
-
/**
|
|
17
|
-
* Template naming conventions (the whole contract between templates/ and here):
|
|
18
|
-
* - `*.hbs` → rendered with the data below, suffix stripped.
|
|
19
|
-
* anything else → copied verbatim (so files with `${{ }}` like ci.yml
|
|
20
|
-
* never meet handlebars).
|
|
21
|
-
* - leading `_` on a basename → `.` (npm pack silently drops/renames real
|
|
22
|
-
* .gitignore files inside packages, so templates ship `_gitignore`).
|
|
23
|
-
*/
|
|
24
|
-
export declare function planCreate(options: CreateOptions, templates: TemplateFile[], render: RenderFn): Blueprint;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type FormDefinition } from 'bank20baht-validator';
|
|
2
|
-
export interface DslField {
|
|
3
|
-
order: number;
|
|
4
|
-
type: string;
|
|
5
|
-
key: string;
|
|
6
|
-
label: string;
|
|
7
|
-
required?: boolean;
|
|
8
|
-
options?: Array<{
|
|
9
|
-
key: string;
|
|
10
|
-
label: string;
|
|
11
|
-
}>;
|
|
12
|
-
min?: number;
|
|
13
|
-
max?: number;
|
|
14
|
-
flag?: string;
|
|
15
|
-
}
|
|
16
|
-
export declare function parseFieldsDsl(dsl: string): DslField[];
|
|
17
|
-
/** DSL -> FormDefinition, engine-validated. Throws FormDslError on any problem. */
|
|
18
|
-
export declare function buildFormDefinition(domain: string, name: string, dsl: string): FormDefinition;
|
|
19
|
-
export declare class FormDslError extends Error {
|
|
20
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { FormDefinition } from 'bank20baht-validator';
|
|
2
|
-
import type { Blueprint } from './blueprint.js';
|
|
3
|
-
/** bahtui.json — written by create/init, read by every `g` command (PRD). */
|
|
4
|
-
export interface BahtuiConfig {
|
|
5
|
-
apps: {
|
|
6
|
-
web?: string;
|
|
7
|
-
api?: string;
|
|
8
|
-
};
|
|
9
|
-
libsDir: string;
|
|
10
|
-
frontend: 'react' | null;
|
|
11
|
-
backend: 'elysia' | null;
|
|
12
|
-
edenClient?: string;
|
|
13
|
-
wrapperPath?: string;
|
|
14
|
-
}
|
|
15
|
-
export declare const DEFAULT_CONFIG: BahtuiConfig;
|
|
16
|
-
export declare function validateDomainName(domain: string): string[];
|
|
17
|
-
/** `bahtui init` — write bahtui.json into an existing repo (root = cwd). */
|
|
18
|
-
export declare function planInit(root: string, config: BahtuiConfig): Blueprint;
|
|
19
|
-
export declare function modelDir(config: BahtuiConfig, domain: string): string;
|
|
20
|
-
/** `bahtui g model <domain>` — the framework-free domain model lib (PRD). */
|
|
21
|
-
export declare function planModel(root: string, config: BahtuiConfig, domain: string): Blueprint;
|
|
22
|
-
/** `bahtui g form <domain>/<name>` — engine-validated FormDefinition JSON. */
|
|
23
|
-
export declare function planForm(root: string, config: BahtuiConfig, domain: string, name: string, form: FormDefinition): Blueprint;
|
package/dist/domain/naming.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { Blueprint } from '../domain/blueprint.js';
|
|
2
|
-
import type { FileWriter } from '../application/ports.js';
|
|
3
|
-
export declare class DiskFileWriter implements FileWriter {
|
|
4
|
-
ensureUsableDir(dir: string, force: boolean): void;
|
|
5
|
-
writeFiles(blueprint: Blueprint): void;
|
|
6
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { TemplateFile } from '../domain/create-plan.js';
|
|
2
|
-
import type { TemplateSource } from '../application/ports.js';
|
|
3
|
-
export declare class DiskTemplateSource implements TemplateSource {
|
|
4
|
-
private readonly root;
|
|
5
|
-
constructor(root?: string);
|
|
6
|
-
load(set: string): TemplateFile[];
|
|
7
|
-
}
|