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 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
- Reads `database/seeds.sql` and inserts dummy data.
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
- ### `flowmo db:query <file> [params-json]`
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
- Executes a `.sql` or `.advance.sql` file against the local database and prints results as an ASCII table.
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 for UI prototyping
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')} Reset and provision the local database from database/schema.sql
27
- ${picocolors.cyan('db:seed')} Insert seed data from database/seeds.sql
28
- ${picocolors.cyan('db:query')} <file> [params-json] Execute a .sql or .advance.sql query file
29
- ${picocolors.dim('--limit <n>')} Max rows to show (default: 10)
30
- ${picocolors.dim('--simple')} Plain key: value output, no truncation
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowmo",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Local, zero-infrastructure prototyping engine for OutSystems-Lite workflows",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -66,13 +66,25 @@ export async function dbQuery(rawArgs = []) {
66
66
  }
67
67
  }
68
68
 
69
- const filePath = positional[0];
70
- const paramsJson = positional.length > 1 ? positional.slice(1).join(' ') : undefined;
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
- if (!filePath) {
73
- throw new Error('Usage: flowmo db:query <file> [params-json] [--limit <n>] [--simple]');
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
+ }
@@ -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
- function resolveFile(filename) {
7
- const candidates = [
8
- path.join(process.cwd(), 'database', filename),
9
- path.join(process.cwd(), filename),
10
- ];
11
- return candidates.find((p) => fs.existsSync(p)) ?? null;
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
- export async function dbSeed() {
15
- const seedsPath = resolveFile('seeds.sql');
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
- if (!seedsPath) {
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
- 'seeds.sql not found. Looked in database/seeds.sql and ./seeds.sql.\nAre you in a Flowmo project directory?'
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
- const seeds = fs.readFileSync(seedsPath, 'utf-8').trim();
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
- await db.exec(seeds);
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(`✓ Database seeded from ${seedsPath}`));
76
+ console.log(picocolors.green(`\n✓ Database seeded (${seedPaths.length} file${seedPaths.length === 1 ? '' : 's'})`));
37
77
  }