flowmo 0.1.0 → 0.2.0
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/README.md +41 -5
- package/bin/flowmo.js +14 -6
- package/package.json +1 -1
- package/src/commands/db-query.js +16 -4
- package/src/commands/db-reset.js +19 -0
- package/src/commands/db-seed.js +59 -19
package/README.md
CHANGED
|
@@ -35,17 +35,52 @@ npx flowmo db:setup
|
|
|
35
35
|
|
|
36
36
|
Run this any time you change your schema.
|
|
37
37
|
|
|
38
|
-
### `flowmo db:seed`
|
|
38
|
+
### `flowmo db:seed [file …]`
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Inserts seed data into the local database. Accepts an optional list of seed files to run in order.
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
+
# Auto-discover: loads database/seeds/ directory (alphabetical) or falls back to database/seeds.sql
|
|
43
44
|
npx flowmo db:seed
|
|
45
|
+
|
|
46
|
+
# Explicit list — executed in the order given
|
|
47
|
+
npx flowmo db:seed database/seeds/01_users.sql database/seeds/02_products.sql
|
|
44
48
|
```
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
**Seed file resolution (no args):**
|
|
51
|
+
1. `database/seeds/` directory exists → all `.sql` files, alphabetical order
|
|
52
|
+
2. `database/seeds.sql` → single file fallback
|
|
53
|
+
|
|
54
|
+
Prefix files with numbers (`01_`, `02_`) to control load order when using the directory.
|
|
55
|
+
|
|
56
|
+
### `flowmo db:reset [--seed [file …]]`
|
|
57
|
+
|
|
58
|
+
Drops and recreates the schema (equivalent to `db:setup`), then optionally seeds.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Recreate schema only
|
|
62
|
+
npx flowmo db:reset
|
|
63
|
+
|
|
64
|
+
# Recreate schema + auto-discover seeds
|
|
65
|
+
npx flowmo db:reset --seed
|
|
66
|
+
|
|
67
|
+
# Recreate schema + explicit seed list
|
|
68
|
+
npx flowmo db:reset --seed database/seeds/01_users.sql database/seeds/02_products.sql
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The `--seed` flag follows the same resolution rules as `db:seed` — files after `--seed` are used as-is; no files after `--seed` triggers auto-discovery.
|
|
72
|
+
|
|
73
|
+
### `flowmo db:query <file|sql> [params-json]`
|
|
74
|
+
|
|
75
|
+
Executes a `.sql` or `.advance.sql` file against the local database and prints results as an ASCII table. Alternatively, pass an inline SQL string directly — no file needed.
|
|
76
|
+
|
|
77
|
+
**Inline SQL:**
|
|
78
|
+
```bash
|
|
79
|
+
npx flowmo db:query "SELECT * FROM users"
|
|
80
|
+
npx flowmo db:query "SELECT COUNT(*) FROM orders WHERE is_active = 1"
|
|
81
|
+
```
|
|
47
82
|
|
|
48
|
-
|
|
83
|
+
Inline mode is param-free. For parameterised queries, use a file.
|
|
49
84
|
|
|
50
85
|
**Standard SQL:**
|
|
51
86
|
```bash
|
|
@@ -90,7 +125,8 @@ A scaffolded Flowmo project looks like this:
|
|
|
90
125
|
my-prototype/
|
|
91
126
|
├── database/
|
|
92
127
|
│ ├── schema.sql # DDL — your single source of truth
|
|
93
|
-
│ ├── seeds.sql # Dummy data
|
|
128
|
+
│ ├── seeds.sql # Dummy data (single file)
|
|
129
|
+
│ ├── seeds/ # OR: split seeds by table (01_users.sql, 02_products.sql …)
|
|
94
130
|
│ └── queries/ # .sql and .advance.sql files
|
|
95
131
|
├── logic/ # .flowchart.md server action flows
|
|
96
132
|
├── screens/ # .visual.html UI prototypes
|
package/bin/flowmo.js
CHANGED
|
@@ -23,17 +23,23 @@ ${picocolors.bold('Usage:')}
|
|
|
23
23
|
flowmo <command> [options]
|
|
24
24
|
|
|
25
25
|
${picocolors.bold('Commands:')}
|
|
26
|
-
${picocolors.cyan('db:setup')}
|
|
27
|
-
${picocolors.cyan('db:seed')}
|
|
28
|
-
${picocolors.cyan('db:
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
${picocolors.cyan('db:setup')} Reset and provision the local database from database/schema.sql
|
|
27
|
+
${picocolors.cyan('db:seed')} [file …] Seed from explicit file(s), database/seeds/ dir, or database/seeds.sql
|
|
28
|
+
${picocolors.cyan('db:reset')} [--seed [file …]] Alias for db:setup; add --seed to also run seeds
|
|
29
|
+
${picocolors.cyan('db:query')} <file|sql> [params-json] Execute a .sql/.advance.sql file or an inline SQL string
|
|
30
|
+
${picocolors.dim('--limit <n>')} Max rows to show (default: 10)
|
|
31
|
+
${picocolors.dim('--simple')} Plain key: value output, no truncation
|
|
31
32
|
|
|
32
33
|
${picocolors.bold('Examples:')}
|
|
33
34
|
flowmo db:setup
|
|
34
35
|
flowmo db:seed
|
|
36
|
+
flowmo db:seed database/seeds/01_users.sql database/seeds/02_products.sql
|
|
37
|
+
flowmo db:reset
|
|
38
|
+
flowmo db:reset --seed
|
|
39
|
+
flowmo db:reset --seed database/seeds/01_users.sql database/seeds/02_products.sql
|
|
35
40
|
flowmo db:query database/queries/get_users.sql
|
|
36
41
|
flowmo db:query database/queries/get_user.advance.sql '{"UserId": 1}'
|
|
42
|
+
flowmo db:query "SELECT * FROM users"
|
|
37
43
|
flowmo db:query database/queries/get_users.sql --limit 25
|
|
38
44
|
flowmo db:query database/queries/get_users.sql --simple
|
|
39
45
|
`;
|
|
@@ -46,11 +52,13 @@ async function run() {
|
|
|
46
52
|
|
|
47
53
|
const { dbSetup } = await import('../src/commands/db-setup.js');
|
|
48
54
|
const { dbSeed } = await import('../src/commands/db-seed.js');
|
|
55
|
+
const { dbReset } = await import('../src/commands/db-reset.js');
|
|
49
56
|
const { dbQuery } = await import('../src/commands/db-query.js');
|
|
50
57
|
|
|
51
58
|
const commands = {
|
|
52
59
|
'db:setup': () => dbSetup(),
|
|
53
|
-
'db:seed': () => dbSeed(),
|
|
60
|
+
'db:seed': () => dbSeed(args),
|
|
61
|
+
'db:reset': () => dbReset(args),
|
|
54
62
|
// Join all args after the file path in case the shell splits the JSON string.
|
|
55
63
|
'db:query': () => dbQuery(args),
|
|
56
64
|
};
|
package/package.json
CHANGED
package/src/commands/db-query.js
CHANGED
|
@@ -66,13 +66,25 @@ export async function dbQuery(rawArgs = []) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
if (positional.length === 0) {
|
|
70
|
+
throw new Error('Usage: flowmo db:query <file.sql> [params-json]\n flowmo db:query "SELECT …" (inline SQL, no params)');
|
|
71
|
+
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
// Inline SQL mode: first positional arg does not end with .sql.
|
|
74
|
+
const looksLikeFile = positional[0].endsWith('.sql');
|
|
75
|
+
if (!looksLikeFile) {
|
|
76
|
+
const inlineSql = positional.join(' ');
|
|
77
|
+
console.log(picocolors.dim(`Query: ${inlineSql}\n`));
|
|
78
|
+
const db = await getDb();
|
|
79
|
+
const result = await db.query(inlineSql, []);
|
|
80
|
+
await closeDb();
|
|
81
|
+
renderTable(result.fields, result.rows, { simple, limit });
|
|
82
|
+
return;
|
|
74
83
|
}
|
|
75
84
|
|
|
85
|
+
const filePath = positional[0];
|
|
86
|
+
const paramsJson = positional.length > 1 ? positional.slice(1).join(' ') : undefined;
|
|
87
|
+
|
|
76
88
|
const resolved = path.resolve(process.cwd(), filePath);
|
|
77
89
|
|
|
78
90
|
if (!fs.existsSync(resolved)) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import picocolors from 'picocolors';
|
|
2
|
+
import { dbSetup } from './db-setup.js';
|
|
3
|
+
import { dbSeed } from './db-seed.js';
|
|
4
|
+
|
|
5
|
+
export async function dbReset(args = []) {
|
|
6
|
+
const seedIdx = args.indexOf('--seed');
|
|
7
|
+
const hasSeed = seedIdx !== -1;
|
|
8
|
+
// Everything after --seed is the explicit file list (empty = auto-discover).
|
|
9
|
+
const seedFiles = hasSeed ? args.slice(seedIdx + 1) : [];
|
|
10
|
+
|
|
11
|
+
await dbSetup();
|
|
12
|
+
|
|
13
|
+
if (hasSeed) {
|
|
14
|
+
console.log('');
|
|
15
|
+
await dbSeed(seedFiles);
|
|
16
|
+
} else {
|
|
17
|
+
console.log(picocolors.dim('\nTip: run with --seed to also seed the database.'));
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/commands/db-seed.js
CHANGED
|
@@ -3,35 +3,75 @@ import path from 'path';
|
|
|
3
3
|
import picocolors from 'picocolors';
|
|
4
4
|
import { getDb, closeDb } from '../lib/db.js';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Resolve seed files to execute, in order.
|
|
8
|
+
*
|
|
9
|
+
* Resolution tiers:
|
|
10
|
+
* 1. Explicit list (args provided) — resolved relative to <cwd>/database/ then <cwd>/
|
|
11
|
+
* 2. Auto-discover database/seeds/ directory — all .sql files alphabetically
|
|
12
|
+
* 3. Fallback to database/seeds.sql or ./seeds.sql
|
|
13
|
+
*/
|
|
14
|
+
function resolveSeedFiles(args) {
|
|
15
|
+
// 1. Explicit list.
|
|
16
|
+
if (args && args.length > 0) {
|
|
17
|
+
return args.map((f) => {
|
|
18
|
+
const candidates = [
|
|
19
|
+
path.join(process.cwd(), 'database', f),
|
|
20
|
+
path.join(process.cwd(), f),
|
|
21
|
+
path.resolve(f),
|
|
22
|
+
];
|
|
23
|
+
const found = candidates.find((p) => fs.existsSync(p));
|
|
24
|
+
if (!found) {
|
|
25
|
+
throw new Error(`Seed file not found: ${f}`);
|
|
26
|
+
}
|
|
27
|
+
return found;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
13
30
|
|
|
14
|
-
|
|
15
|
-
const
|
|
31
|
+
// 2. Auto-discover seeds/ directory.
|
|
32
|
+
const seedsDir = path.join(process.cwd(), 'database', 'seeds');
|
|
33
|
+
if (fs.existsSync(seedsDir) && fs.statSync(seedsDir).isDirectory()) {
|
|
34
|
+
const files = fs.readdirSync(seedsDir)
|
|
35
|
+
.filter((f) => f.endsWith('.sql'))
|
|
36
|
+
.sort()
|
|
37
|
+
.map((f) => path.join(seedsDir, f));
|
|
38
|
+
if (files.length > 0) return files;
|
|
39
|
+
}
|
|
16
40
|
|
|
17
|
-
|
|
41
|
+
// 3. Fallback: single seeds.sql.
|
|
42
|
+
const candidates = [
|
|
43
|
+
path.join(process.cwd(), 'database', 'seeds.sql'),
|
|
44
|
+
path.join(process.cwd(), 'seeds.sql'),
|
|
45
|
+
];
|
|
46
|
+
const found = candidates.find((p) => fs.existsSync(p));
|
|
47
|
+
if (!found) {
|
|
18
48
|
throw new Error(
|
|
19
|
-
'
|
|
49
|
+
'No seed files found. Looked for database/seeds/, database/seeds.sql, and ./seeds.sql.\nAre you in a Flowmo project directory?'
|
|
20
50
|
);
|
|
21
51
|
}
|
|
52
|
+
return [found];
|
|
53
|
+
}
|
|
22
54
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!seeds) {
|
|
26
|
-
throw new Error(`${seedsPath} is empty. Add your INSERT statements first.`);
|
|
27
|
-
}
|
|
55
|
+
export async function dbSeed(files = []) {
|
|
56
|
+
const seedPaths = resolveSeedFiles(files);
|
|
28
57
|
|
|
29
58
|
console.log(picocolors.cyan('Seeding database…'));
|
|
30
|
-
console.log(picocolors.dim(`Using: ${seedsPath}`));
|
|
31
59
|
|
|
32
60
|
const db = await getDb();
|
|
33
|
-
|
|
61
|
+
|
|
62
|
+
for (const seedsPath of seedPaths) {
|
|
63
|
+
const seeds = fs.readFileSync(seedsPath, 'utf-8').trim();
|
|
64
|
+
|
|
65
|
+
if (!seeds) {
|
|
66
|
+
throw new Error(`${seedsPath} is empty. Add your INSERT statements first.`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(picocolors.dim(`Running: ${seedsPath}`));
|
|
70
|
+
await db.exec(seeds);
|
|
71
|
+
console.log(picocolors.green(`✓ ${path.basename(seedsPath)}`));
|
|
72
|
+
}
|
|
73
|
+
|
|
34
74
|
await closeDb();
|
|
35
75
|
|
|
36
|
-
console.log(picocolors.green(
|
|
76
|
+
console.log(picocolors.green(`\n✓ Database seeded (${seedPaths.length} file${seedPaths.length === 1 ? '' : 's'})`));
|
|
37
77
|
}
|