@sebspark/spanner-migrate 0.2.0 → 1.0.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
@@ -14,110 +14,196 @@ yarn add -D @sebspark/spanner-migrate
14
14
 
15
15
  ---
16
16
 
17
- ## CLI Usage
17
+ ## CLI Commands
18
18
 
19
- Run `spanner-migrate` from your project root. If no command is provided, the help message is displayed.
19
+ `spanner-migrate` provides several commands for managing database migrations in Google Spanner.
20
20
 
21
- ```zsh
22
- spanner-migrate [command] [options]
23
- ```
21
+ ### Initialize Configuration
24
22
 
25
- ### Commands
23
+ ```sh
24
+ spanner-migrate init
25
+ ```
26
26
 
27
- #### `init`
28
- Initialize a Spanner migration configuration file (`.spanner-migrate.config.json`).
27
+ Initializes a `.spanner-migrate.config.json` file by prompting for:
28
+ - Spanner instance name
29
+ - One or more database configurations
30
+ - Optional Google Cloud project name
29
31
 
30
- **Usage:**
32
+ ### Create a Migration
31
33
 
32
- ```zsh
33
- spanner-migrate init
34
+ ```sh
35
+ spanner-migrate create <description ...> [--database <name>]
36
+ spanner-migrate create add users table
37
+ spanner-migrate create --database=mydb add users table
34
38
  ```
35
39
 
36
- **Prompts:**
37
- - `Enter the path for your migrations`: Directory for migration files (default: `./migrations`).
38
- - `Enter Spanner instance name`: The name of the Spanner instance.
39
- - `Enter Spanner database name`: The name of the Spanner database.
40
- - `Enter Google Cloud project name`: (Optional) The Google Cloud project name.
40
+ Creates a new migration file with the specified description.
41
41
 
42
- ---
42
+ - If `--database` (`-d`) is provided, it uses the specified database.
43
+ - If multiple databases exist and none is specified, the user is prompted to select one.
44
+ - The filename is generated from the description (`<timestamp>_add_users_table.sql`).
43
45
 
44
- #### `create <description>`
45
- Create a new migration file.
46
+ ### Apply Migrations
46
47
 
47
- **Usage:**
48
+ ```sh
49
+ spanner-migrate up
50
+ spanner-migrate up --database <name>
51
+ spanner-migrate up --database <name> --max <n>
52
+ ```
48
53
 
49
- ```zsh
50
- spanner-migrate create add users table
54
+ Applies pending migrations.
55
+
56
+ - If **no** `--database` and `--max` are provided, applies all migrations to all databases.
57
+ - If `--database` (`-d`) is provided, applies migrations only to that database.
58
+ - If `--max` (`-m`) is provided, limits the number of migrations applied (requires `--database`).
59
+ - `--max` must be an integer greater than 0.
60
+
61
+ ### Roll Back Last Migration
62
+
63
+ ```sh
64
+ spanner-migrate down
65
+ spanner-migrate down --database <name>
51
66
  ```
52
67
 
53
- **Result:**
68
+ Rolls back the last applied migration.
69
+
70
+ - If a **single** database exists, it is automatically selected.
71
+ - If multiple databases exist, `--database` is **required**.
72
+ - The specified `--database` must exist.
73
+
74
+ ### Show Migration Status
54
75
 
55
- Example:
76
+ ```sh
77
+ spanner-migrate status
78
+ spanner-migrate status --database <name>
79
+ ```
80
+
81
+ Displays migration status.
56
82
 
57
- `./migrations/20250120145638000_create_table_users.sql`
83
+ - If `--database` is specified, shows status for that database.
84
+ - If no `--database` is provided, shows status for all configured databases.
58
85
 
59
- ```sql
60
- -- Created: 2025-01-20T14:56:38.000Z
61
- -- Description: create table users
86
+ ### Help
62
87
 
63
- ---- UP ----
88
+ ```sh
89
+ spanner-migrate --help
90
+ spanner-migrate <command> --help
91
+ ```
64
92
 
93
+ Displays help for the CLI or a specific command.
65
94
 
95
+ ---
66
96
 
67
- ---- DOWN ----
97
+ ## Programmatic Usage
68
98
 
99
+ In addition to the CLI, `spanner-migrate` can be used as a Node.js module to manage migrations programmatically.
69
100
 
101
+ ### Importing
70
102
 
103
+ ```typescript
104
+ import { init, create, up, down, status } from '@sebspark/spanner-migrate'
71
105
  ```
72
106
 
73
- #### `up`
74
- Apply pending migrations
107
+ ### Initializing Configuration
75
108
 
76
- **Usage:**
109
+ ```typescript
110
+ import { init, type Config } from '@sebspark/spanner-migrate'
77
111
 
78
- ```zsh
79
- spanner-migrate up
112
+ const config: Config = {
113
+ instance: {
114
+ name: 'my-instance',
115
+ databases: [
116
+ { name: 'mydb', migrationsPath: './migrations' },
117
+ ],
118
+ },
119
+ projectId: 'my-gcp-project',
120
+ }
121
+
122
+ await init(config, '.spanner-migrate.config.json')
80
123
  ```
81
124
 
82
- If you don't want to apply all pending migrations, use the `--max` or `-m` flag
125
+ Writes the given configuration to a `.spanner-migrate.config.json` file.
83
126
 
84
- ```zsh
85
- spanner migrate up --max 1
127
+ ### Creating a Migration
128
+
129
+ ```typescript
130
+ import { create, type DatabaseConfig } from '@sebspark/spanner-migrate'
131
+
132
+ const databaseConfig: DatabaseConfig = {
133
+ name: 'mydb',
134
+ migrationsPath: './migrations',
135
+ }
136
+
137
+ await create(databaseConfig, 'add users table')
86
138
  ```
87
139
 
88
- #### `down`
89
- Rollback one migration
140
+ Creates a new migration file for the specified database.
90
141
 
91
- **Usage:**
142
+ ### Applying Migrations
92
143
 
93
- ```zsh
94
- spanner-migrate down
144
+ ```typescript
145
+ import { up, type Config, type DatabaseConfig } from '@sebspark/spanner-migrate'
146
+
147
+ // Load configuration
148
+ const config: Config = /* Load from file or define inline */
149
+
150
+ // Apply all migrations to all databases
151
+ await up(config)
152
+
153
+ // Apply all migrations to a specific database
154
+ const databaseConfig: DatabaseConfig = config.instance.databases[0]
155
+ await up(config, databaseConfig)
156
+
157
+ // Apply up to 5 migrations to a specific database
158
+ await up(config, databaseConfig, 5)
95
159
  ```
96
160
 
97
- #### `status`
98
- Check migration status
161
+ - Applies pending migrations.
162
+ - If a database is specified, only applies migrations to that database.
163
+ - If `max` is specified, applies at most `max` migrations.
99
164
 
100
- **Usage:**
165
+ ### Rolling Back Migrations
101
166
 
102
- ```zsh
103
- spanner-migrate status
167
+ ```typescript
168
+ import { up, type Config, type DatabaseConfig } from '@sebspark/spanner-migrate'
169
+
170
+ const config: Config = /* Load from file */
171
+ const databaseConfig: DatabaseConfig = config.instance.databases[0]
172
+
173
+ // Roll back the last applied migration
174
+ await down(config, databaseConfig)
104
175
  ```
105
- Displays an overview of applied and peding migrations
106
176
 
107
- ```text
108
- Migrations
177
+ - Rolls back the last applied migration for the specified database.
178
+ - Requires a database to be specified.
179
+
180
+ ### Checking Migration Status
181
+
182
+ ```typescript
183
+ import { up, type Config, type DatabaseConfig } from '@sebspark/spanner-migrate'
109
184
 
110
- Applied
111
- --------------------------------------------------------------------------------
112
- 20250122080434866_add_users_table
113
- 20250122080444982_add_index_on_users
185
+ const config: Config = /* Load from file */
114
186
 
115
- New
116
- --------------------------------------------------------------------------------
117
- 20250122080444982_add_index_on_users
187
+ // Check status for all databases
188
+ const migrationStatus = await status(config)
189
+ console.log(migrationStatus)
190
+
191
+ // Check status for a specific database
192
+ const databaseConfig = config.instance.databases[0]
193
+ const migrationStatusSingle = await status(config, [databaseConfig])
194
+ console.log(migrationStatusSingle)
118
195
  ```
119
196
 
120
- ---
197
+ - Displays applied and pending migrations for one or more databases.
198
+ - If a specific database is provided, only its status is shown.
199
+
200
+ ## Running on Spanner Emulator
201
+
202
+ If you want to test your migrations against a Spanner Emulator, you will need to set:
203
+
204
+ ```typescript
205
+ process.env.SPANNER_EMULATOR_HOST = 'localhost:<port>'
206
+ ```
121
207
 
122
208
  ## License
123
209
 
@@ -38,7 +38,7 @@ var applyDown = async (db) => {
38
38
  json: true
39
39
  };
40
40
  const [rows] = await db.run(req);
41
- const lastMigration = rows == null ? void 0 : rows[0];
41
+ const lastMigration = rows?.[0];
42
42
  if (!lastMigration) {
43
43
  throw new Error("No migrations found to roll back.");
44
44
  }
@@ -100,12 +100,12 @@ var SQL_CREATE_TABLE_MIGRATIONS = `
100
100
  down STRING(1024)
101
101
  ) PRIMARY KEY (id)
102
102
  `;
103
- var ensureMigrationTable = async (database) => {
104
- const [rows] = await database.run(SQL_SELECT_TABLE_MIGRATIONS);
103
+ var ensureMigrationTable = async (db) => {
104
+ const [rows] = await db.run(SQL_SELECT_TABLE_MIGRATIONS);
105
105
  if (rows.length) return;
106
106
  console.log("Creating migration table");
107
107
  try {
108
- await database.updateSchema(SQL_CREATE_TABLE_MIGRATIONS);
108
+ await db.updateSchema(SQL_CREATE_TABLE_MIGRATIONS);
109
109
  } catch (err) {
110
110
  console.error("Failed to create migrations table");
111
111
  throw err;
@@ -168,22 +168,17 @@ var getMigration = async (path, id) => {
168
168
  );
169
169
  }
170
170
  };
171
- var getDescription = (text) => {
172
- var _a, _b;
173
- return ((_b = (_a = text == null ? void 0 : text.match(/^--\s*Description:\s*(.+)$/m)) == null ? void 0 : _a[1]) == null ? void 0 : _b.trim()) || "";
174
- };
171
+ var getDescription = (text) => text?.match(/^--\s*Description:\s*(.+)$/m)?.[1]?.trim() || "";
175
172
  var getSql = (text, direction) => {
176
- var _a, _b;
177
173
  const rx = {
178
174
  up: /---- UP ----\n([\s\S]*?)\n---- DOWN ----/,
179
175
  down: /---- DOWN ----\n([\s\S]*)$/
180
176
  };
181
- return (_b = (_a = text == null ? void 0 : text.match(rx[direction])) == null ? void 0 : _a[1]) == null ? void 0 : _b.replace(/--.*$/gm, "").trim();
177
+ return text?.match(rx[direction])?.[1]?.replace(/--.*$/gm, "").trim();
182
178
  };
183
179
  var getNewMigrations = (applied, files) => {
184
180
  const sortedFiles = files.sort();
185
181
  for (let ix = 0; ix < applied.length; ix++) {
186
- console.log(sortedFiles[ix], applied[ix].id);
187
182
  if (sortedFiles[ix] !== applied[ix].id) {
188
183
  throw new Error(
189
184
  `Mismatch between applied migrations and files. Found '${sortedFiles[ix]}' but expected '${applied[ix].id}' at position ${ix}.`
@@ -191,7 +186,6 @@ var getNewMigrations = (applied, files) => {
191
186
  }
192
187
  }
193
188
  const newMigrations = sortedFiles.slice(applied.length);
194
- console.log(`Found ${newMigrations.length} new migrations.`);
195
189
  return newMigrations;
196
190
  };
197
191
  var createMigration = async (path, description) => {
@@ -242,42 +236,74 @@ var init = async (config, configPath) => {
242
236
  var create = async (config, description) => {
243
237
  await createMigration(config.migrationsPath, description);
244
238
  };
245
- var up = async (config, max = 1e3) => {
246
- const db = getDb(config);
247
- await ensureMigrationTable(db);
248
- const appliedMigrations = await getAppliedMigrations(db);
249
- const migrationFiles = await getMigrationFiles(config.migrationsPath);
250
- const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
251
- console.log(`Found ${newMigrations.length} new migrations.`);
252
- console.log(newMigrations.map((mig) => ` ${mig}`).join("\n"));
253
- for (const id of newMigrations.slice(0, max)) {
254
- const migration = await getMigration(config.migrationsPath, id);
255
- await applyUp(db, migration);
239
+ var up = async (config, database, max) => {
240
+ if (max && !database) {
241
+ throw new Error("Max number of migrations requires specifying a database");
242
+ }
243
+ const databases = database ? [database] : config.instance.databases;
244
+ for (const databaseConfig of databases) {
245
+ const path = {
246
+ projectId: config.projectId,
247
+ instanceName: config.instance.name,
248
+ databaseName: databaseConfig.name
249
+ };
250
+ const db = getDb(path);
251
+ await ensureMigrationTable(db);
252
+ const appliedMigrations = await getAppliedMigrations(db);
253
+ const migrationFiles = await getMigrationFiles(
254
+ databaseConfig.migrationsPath
255
+ );
256
+ const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
257
+ console.log(`Found ${newMigrations.length} new migrations.`);
258
+ console.log(newMigrations.map((mig) => ` ${mig}`).join("\n"));
259
+ const newMigrationsToApply = max ? newMigrations.slice(0, max) : newMigrations;
260
+ for (const id of newMigrationsToApply) {
261
+ const migration = await getMigration(databaseConfig.migrationsPath, id);
262
+ await applyUp(db, migration);
263
+ }
256
264
  }
257
265
  };
258
- var down = async (config) => {
259
- const db = getDb(config);
266
+ var down = async (config, database) => {
267
+ const path = {
268
+ projectId: config.projectId,
269
+ instanceName: config.instance.name,
270
+ databaseName: database.name
271
+ };
272
+ const db = getDb(path);
260
273
  await ensureMigrationTable(db);
261
274
  await applyDown(db);
262
275
  };
263
- var status = async (config) => {
264
- const db = getDb(config);
265
- await ensureMigrationTable(db);
266
- const appliedMigrations = await getAppliedMigrations(db);
267
- const migrationFiles = await getMigrationFiles(config.migrationsPath);
268
- const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
269
- return [
270
- "Migrations",
271
- "",
272
- "Applied",
273
- "--------------------------------------------------------------------------------",
274
- `${appliedMigrations.map((m) => m.id).join("\n")}
276
+ var status = async (config, databases) => {
277
+ const statuses = [];
278
+ for (const databaseConfig of databases || config.instance.databases) {
279
+ const path = {
280
+ projectId: config.projectId,
281
+ instanceName: config.instance.name,
282
+ databaseName: databaseConfig.name
283
+ };
284
+ const db = getDb(path);
285
+ await ensureMigrationTable(db);
286
+ const appliedMigrations = await getAppliedMigrations(db);
287
+ const migrationFiles = await getMigrationFiles(
288
+ databaseConfig.migrationsPath
289
+ );
290
+ const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
291
+ statuses.push(
292
+ [
293
+ `Migrations [${databaseConfig.name}]`,
294
+ "",
295
+ "Applied",
296
+ "--------------------------------------------------------------------------------",
297
+ `${appliedMigrations.map((m) => m.id).join("\n")}
275
298
  `,
276
- "New",
277
- "--------------------------------------------------------------------------------",
278
- `${newMigrations.join("\n")}
299
+ "New",
300
+ "--------------------------------------------------------------------------------",
301
+ `${newMigrations.join("\n")}
279
302
  `
280
- ].join("\n");
303
+ ].join("\n")
304
+ );
305
+ }
306
+ return statuses.join("\n\n");
281
307
  };
282
308
 
283
309
  export {
package/dist/cli.js CHANGED
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/cli.ts
27
27
  var import_promises2 = __toESM(require("fs/promises"));
28
28
  var import_node_path2 = require("path");
29
- var import_input = __toESM(require("@inquirer/input"));
29
+ var import_prompts = require("@inquirer/prompts");
30
30
  var import_yargs = __toESM(require("yargs"));
31
31
  var import_helpers = require("yargs/helpers");
32
32
 
@@ -70,7 +70,7 @@ var applyDown = async (db) => {
70
70
  json: true
71
71
  };
72
72
  const [rows] = await db.run(req);
73
- const lastMigration = rows == null ? void 0 : rows[0];
73
+ const lastMigration = rows?.[0];
74
74
  if (!lastMigration) {
75
75
  throw new Error("No migrations found to roll back.");
76
76
  }
@@ -132,12 +132,12 @@ var SQL_CREATE_TABLE_MIGRATIONS = `
132
132
  down STRING(1024)
133
133
  ) PRIMARY KEY (id)
134
134
  `;
135
- var ensureMigrationTable = async (database) => {
136
- const [rows] = await database.run(SQL_SELECT_TABLE_MIGRATIONS);
135
+ var ensureMigrationTable = async (db) => {
136
+ const [rows] = await db.run(SQL_SELECT_TABLE_MIGRATIONS);
137
137
  if (rows.length) return;
138
138
  console.log("Creating migration table");
139
139
  try {
140
- await database.updateSchema(SQL_CREATE_TABLE_MIGRATIONS);
140
+ await db.updateSchema(SQL_CREATE_TABLE_MIGRATIONS);
141
141
  } catch (err) {
142
142
  console.error("Failed to create migrations table");
143
143
  throw err;
@@ -200,22 +200,17 @@ var getMigration = async (path, id) => {
200
200
  );
201
201
  }
202
202
  };
203
- var getDescription = (text) => {
204
- var _a, _b;
205
- return ((_b = (_a = text == null ? void 0 : text.match(/^--\s*Description:\s*(.+)$/m)) == null ? void 0 : _a[1]) == null ? void 0 : _b.trim()) || "";
206
- };
203
+ var getDescription = (text) => text?.match(/^--\s*Description:\s*(.+)$/m)?.[1]?.trim() || "";
207
204
  var getSql = (text, direction) => {
208
- var _a, _b;
209
205
  const rx = {
210
206
  up: /---- UP ----\n([\s\S]*?)\n---- DOWN ----/,
211
207
  down: /---- DOWN ----\n([\s\S]*)$/
212
208
  };
213
- return (_b = (_a = text == null ? void 0 : text.match(rx[direction])) == null ? void 0 : _a[1]) == null ? void 0 : _b.replace(/--.*$/gm, "").trim();
209
+ return text?.match(rx[direction])?.[1]?.replace(/--.*$/gm, "").trim();
214
210
  };
215
211
  var getNewMigrations = (applied, files) => {
216
212
  const sortedFiles = files.sort();
217
213
  for (let ix = 0; ix < applied.length; ix++) {
218
- console.log(sortedFiles[ix], applied[ix].id);
219
214
  if (sortedFiles[ix] !== applied[ix].id) {
220
215
  throw new Error(
221
216
  `Mismatch between applied migrations and files. Found '${sortedFiles[ix]}' but expected '${applied[ix].id}' at position ${ix}.`
@@ -223,7 +218,6 @@ var getNewMigrations = (applied, files) => {
223
218
  }
224
219
  }
225
220
  const newMigrations = sortedFiles.slice(applied.length);
226
- console.log(`Found ${newMigrations.length} new migrations.`);
227
221
  return newMigrations;
228
222
  };
229
223
  var createMigration = async (path, description) => {
@@ -274,86 +268,116 @@ var init = async (config, configPath) => {
274
268
  var create = async (config, description) => {
275
269
  await createMigration(config.migrationsPath, description);
276
270
  };
277
- var up = async (config, max = 1e3) => {
278
- const db = getDb(config);
279
- await ensureMigrationTable(db);
280
- const appliedMigrations = await getAppliedMigrations(db);
281
- const migrationFiles = await getMigrationFiles(config.migrationsPath);
282
- const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
283
- console.log(`Found ${newMigrations.length} new migrations.`);
284
- console.log(newMigrations.map((mig) => ` ${mig}`).join("\n"));
285
- for (const id of newMigrations.slice(0, max)) {
286
- const migration = await getMigration(config.migrationsPath, id);
287
- await applyUp(db, migration);
271
+ var up = async (config, database, max) => {
272
+ if (max && !database) {
273
+ throw new Error("Max number of migrations requires specifying a database");
274
+ }
275
+ const databases = database ? [database] : config.instance.databases;
276
+ for (const databaseConfig of databases) {
277
+ const path = {
278
+ projectId: config.projectId,
279
+ instanceName: config.instance.name,
280
+ databaseName: databaseConfig.name
281
+ };
282
+ const db = getDb(path);
283
+ await ensureMigrationTable(db);
284
+ const appliedMigrations = await getAppliedMigrations(db);
285
+ const migrationFiles = await getMigrationFiles(
286
+ databaseConfig.migrationsPath
287
+ );
288
+ const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
289
+ console.log(`Found ${newMigrations.length} new migrations.`);
290
+ console.log(newMigrations.map((mig) => ` ${mig}`).join("\n"));
291
+ const newMigrationsToApply = max ? newMigrations.slice(0, max) : newMigrations;
292
+ for (const id of newMigrationsToApply) {
293
+ const migration = await getMigration(databaseConfig.migrationsPath, id);
294
+ await applyUp(db, migration);
295
+ }
288
296
  }
289
297
  };
290
- var down = async (config) => {
291
- const db = getDb(config);
298
+ var down = async (config, database) => {
299
+ const path = {
300
+ projectId: config.projectId,
301
+ instanceName: config.instance.name,
302
+ databaseName: database.name
303
+ };
304
+ const db = getDb(path);
292
305
  await ensureMigrationTable(db);
293
306
  await applyDown(db);
294
307
  };
295
- var status = async (config) => {
296
- const db = getDb(config);
297
- await ensureMigrationTable(db);
298
- const appliedMigrations = await getAppliedMigrations(db);
299
- const migrationFiles = await getMigrationFiles(config.migrationsPath);
300
- const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
301
- return [
302
- "Migrations",
303
- "",
304
- "Applied",
305
- "--------------------------------------------------------------------------------",
306
- `${appliedMigrations.map((m) => m.id).join("\n")}
308
+ var status = async (config, databases) => {
309
+ const statuses = [];
310
+ for (const databaseConfig of databases || config.instance.databases) {
311
+ const path = {
312
+ projectId: config.projectId,
313
+ instanceName: config.instance.name,
314
+ databaseName: databaseConfig.name
315
+ };
316
+ const db = getDb(path);
317
+ await ensureMigrationTable(db);
318
+ const appliedMigrations = await getAppliedMigrations(db);
319
+ const migrationFiles = await getMigrationFiles(
320
+ databaseConfig.migrationsPath
321
+ );
322
+ const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
323
+ statuses.push(
324
+ [
325
+ `Migrations [${databaseConfig.name}]`,
326
+ "",
327
+ "Applied",
328
+ "--------------------------------------------------------------------------------",
329
+ `${appliedMigrations.map((m) => m.id).join("\n")}
307
330
  `,
308
- "New",
309
- "--------------------------------------------------------------------------------",
310
- `${newMigrations.join("\n")}
331
+ "New",
332
+ "--------------------------------------------------------------------------------",
333
+ `${newMigrations.join("\n")}
311
334
  `
312
- ].join("\n");
335
+ ].join("\n")
336
+ );
337
+ }
338
+ return statuses.join("\n\n");
313
339
  };
314
340
 
315
341
  // src/cli.ts
316
342
  var CONFIG_FILE = "./.spanner-migrate.config.json";
317
- async function loadConfig() {
318
- try {
319
- const configContent = await import_promises2.default.readFile(CONFIG_FILE, "utf8");
320
- return JSON.parse(configContent);
321
- } catch {
322
- console.error('Config file not found. Run "spanner-migrate init" first.');
323
- process.exit(1);
324
- }
325
- }
326
343
  (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).scriptName("spanner-migrate").usage("$0 <command>").command(
327
344
  "init",
328
345
  "Initialize a .spanner-migrate.config.json file",
329
346
  async () => {
330
- const migrationsPath = await (0, import_input.default)({
331
- message: "Enter the path for your migrations",
332
- required: true,
333
- default: "./migrations"
334
- });
335
- const instanceName = await (0, import_input.default)({
347
+ const instanceName = await (0, import_prompts.input)({
336
348
  message: "Enter Spanner instance name",
337
349
  required: true
338
350
  });
339
- const databaseName = await (0, import_input.default)({
340
- message: "Enter Spanner database name",
341
- required: true
342
- });
343
- const projectId = await (0, import_input.default)({
351
+ const databases = [
352
+ await getDatabaseConfig(true)
353
+ ];
354
+ while (true) {
355
+ const dbConfig = await getDatabaseConfig(false);
356
+ if (!dbConfig) break;
357
+ databases.push(dbConfig);
358
+ }
359
+ const projectId = await (0, import_prompts.input)({
344
360
  message: "Enter Google Cloud project name",
345
361
  required: false
346
362
  });
347
- const config = { instanceName, databaseName, migrationsPath };
363
+ const config = {
364
+ instance: {
365
+ name: instanceName,
366
+ databases
367
+ }
368
+ };
348
369
  if (projectId) config.projectId = projectId;
349
370
  await init(config, CONFIG_FILE);
350
- console.log(`Configuration written to ${CONFIG_FILE}`);
351
371
  }
352
372
  ).command(
353
373
  "create <description ...>",
354
374
  "Create a new migration file",
355
375
  (yargs2) => {
356
- yargs2.positional("description", {
376
+ yargs2.option("database", {
377
+ alias: "d",
378
+ type: "string",
379
+ describe: "Database name"
380
+ }).positional("description", {
357
381
  type: "string",
358
382
  describe: "Description of the migration",
359
383
  demandOption: true
@@ -362,33 +386,157 @@ async function loadConfig() {
362
386
  async (args) => {
363
387
  const config = await loadConfig();
364
388
  const fullDescription = args.description.join(" ");
365
- await create(config, fullDescription);
389
+ let databaseConfig;
390
+ if (args.database) {
391
+ databaseConfig = config.instance.databases.find(
392
+ (db) => db.name === args.database
393
+ );
394
+ if (!databaseConfig) {
395
+ throw new Error(`Unknown database name "${args.database}"`);
396
+ }
397
+ } else {
398
+ if (config.instance.databases.length === 1) {
399
+ databaseConfig = config.instance.databases[0];
400
+ } else {
401
+ databaseConfig = await (0, import_prompts.select)({
402
+ message: "Select database",
403
+ choices: config.instance.databases.map((dbConfig) => ({
404
+ name: dbConfig.name,
405
+ value: dbConfig
406
+ }))
407
+ });
408
+ }
409
+ }
410
+ if (!databaseConfig) throw new Error("No database config found");
411
+ await create(databaseConfig, fullDescription);
366
412
  console.log(
367
- `Migration file created: '${(0, import_node_path2.join)(config.migrationsPath, args.description.join("_"))}.sql'`
413
+ `Migration file created: '${(0, import_node_path2.join)(databaseConfig.migrationsPath, args.description.join("_"))}.sql'`
368
414
  );
369
415
  }
370
416
  ).command(
371
417
  "up",
372
418
  "Apply migrations",
373
419
  (yargs2) => {
374
- yargs2.option("max", {
420
+ yargs2.option("database", {
421
+ alias: "d",
422
+ type: "string",
423
+ describe: "Database name",
424
+ requiresArg: false
425
+ }).option("max", {
375
426
  alias: "m",
376
427
  type: "number",
377
- describe: "Maximum number of migrations to apply",
378
- default: 1e3
428
+ describe: "Maximum number of migrations to apply (requires --database)",
429
+ requiresArg: false
379
430
  });
380
431
  },
381
432
  async (args) => {
382
433
  const config = await loadConfig();
383
- await up(config, args.max);
434
+ if (args.max !== void 0) {
435
+ if (!args.database) {
436
+ throw new Error("The --max option requires a specified --database");
437
+ }
438
+ if (!Number.isInteger(args.max) || args.max <= 0) {
439
+ throw new Error("The --max option must be an integer greater than 0");
440
+ }
441
+ }
442
+ if (args.database) {
443
+ const databaseConfig = config.instance.databases.find(
444
+ (db) => db.name === args.database
445
+ );
446
+ if (!databaseConfig) {
447
+ throw new Error(`Unknown database name "${args.database}"`);
448
+ }
449
+ if (args.max !== void 0) {
450
+ await up(config, databaseConfig, args.max);
451
+ } else {
452
+ await up(config, databaseConfig);
453
+ }
454
+ } else {
455
+ await up(config);
456
+ }
384
457
  console.log("Migrations applied successfully.");
385
458
  }
386
- ).command("down", "Roll back the last applied migration", {}, async () => {
387
- const config = await loadConfig();
388
- await down(config);
389
- console.log("Last migration rolled back successfully.");
390
- }).command("status", "Show the migration status", {}, async () => {
391
- const config = await loadConfig();
392
- const migrationStatus = await status(config);
393
- console.log(migrationStatus);
394
- }).demandCommand().help().parse();
459
+ ).command(
460
+ "down",
461
+ "Roll back the last applied migration",
462
+ (yargs2) => {
463
+ yargs2.option("database", {
464
+ alias: "d",
465
+ type: "string",
466
+ describe: "Specify the database to roll back (required if multiple databases exist)"
467
+ });
468
+ },
469
+ async (args) => {
470
+ const config = await loadConfig();
471
+ let databaseConfig;
472
+ if (args.database) {
473
+ databaseConfig = config.instance.databases.find(
474
+ (dbConfig) => dbConfig.name === args.database
475
+ );
476
+ if (!databaseConfig) {
477
+ throw new Error(`Unknown database name "${args.database}"`);
478
+ }
479
+ } else if (config.instance.databases.length === 1) {
480
+ databaseConfig = config.instance.databases[0];
481
+ } else {
482
+ throw new Error(
483
+ "Multiple databases detected. Use --database to specify which one to roll back."
484
+ );
485
+ }
486
+ if (!databaseConfig) throw new Error("No database config found");
487
+ await down(config, databaseConfig);
488
+ console.log("Last migration rolled back successfully.");
489
+ }
490
+ ).command(
491
+ "status",
492
+ "Show the migration status",
493
+ (yargs2) => {
494
+ yargs2.option("database", {
495
+ alias: "d",
496
+ type: "string",
497
+ describe: "Specify a database to check status (optional, runs on all databases if omitted)"
498
+ });
499
+ },
500
+ async (args) => {
501
+ const config = await loadConfig();
502
+ let migrationStatus;
503
+ if (args.database) {
504
+ const databaseConfig = config.instance.databases.find(
505
+ (db) => db.name === args.database
506
+ );
507
+ if (!databaseConfig) {
508
+ throw new Error(`Unknown database name "${args.database}"`);
509
+ }
510
+ migrationStatus = await status(config, [databaseConfig]);
511
+ } else {
512
+ migrationStatus = await status(config);
513
+ }
514
+ console.log(migrationStatus);
515
+ }
516
+ ).demandCommand().help().parse();
517
+ async function loadConfig() {
518
+ try {
519
+ const configContent = await import_promises2.default.readFile(CONFIG_FILE, "utf8");
520
+ return JSON.parse(configContent);
521
+ } catch {
522
+ console.error('Config file not found. Run "spanner-migrate init" first.');
523
+ process.exit(1);
524
+ }
525
+ }
526
+ var getDatabaseConfig = async (required) => {
527
+ const message = required ? "Enter Spanner database name" : "Enter another Spanner database name [Enter to continue]";
528
+ const name = await (0, import_prompts.input)({
529
+ message,
530
+ required
531
+ });
532
+ if (!name) return;
533
+ const migrationsPath = await (0, import_prompts.input)({
534
+ message: "Enter the path for your migrations",
535
+ required: true,
536
+ default: `./migrations/${name}`
537
+ });
538
+ return {
539
+ name,
540
+ migrationsPath
541
+ };
542
+ };
package/dist/cli.mjs CHANGED
@@ -5,55 +5,53 @@ import {
5
5
  init,
6
6
  status,
7
7
  up
8
- } from "./chunk-Z3ZLNDVO.mjs";
8
+ } from "./chunk-SZDH364K.mjs";
9
9
 
10
10
  // src/cli.ts
11
11
  import fs from "node:fs/promises";
12
12
  import { join } from "node:path";
13
- import input from "@inquirer/input";
13
+ import { input, select } from "@inquirer/prompts";
14
14
  import yargs from "yargs";
15
15
  import { hideBin } from "yargs/helpers";
16
16
  var CONFIG_FILE = "./.spanner-migrate.config.json";
17
- async function loadConfig() {
18
- try {
19
- const configContent = await fs.readFile(CONFIG_FILE, "utf8");
20
- return JSON.parse(configContent);
21
- } catch {
22
- console.error('Config file not found. Run "spanner-migrate init" first.');
23
- process.exit(1);
24
- }
25
- }
26
17
  yargs(hideBin(process.argv)).scriptName("spanner-migrate").usage("$0 <command>").command(
27
18
  "init",
28
19
  "Initialize a .spanner-migrate.config.json file",
29
20
  async () => {
30
- const migrationsPath = await input({
31
- message: "Enter the path for your migrations",
32
- required: true,
33
- default: "./migrations"
34
- });
35
21
  const instanceName = await input({
36
22
  message: "Enter Spanner instance name",
37
23
  required: true
38
24
  });
39
- const databaseName = await input({
40
- message: "Enter Spanner database name",
41
- required: true
42
- });
25
+ const databases = [
26
+ await getDatabaseConfig(true)
27
+ ];
28
+ while (true) {
29
+ const dbConfig = await getDatabaseConfig(false);
30
+ if (!dbConfig) break;
31
+ databases.push(dbConfig);
32
+ }
43
33
  const projectId = await input({
44
34
  message: "Enter Google Cloud project name",
45
35
  required: false
46
36
  });
47
- const config = { instanceName, databaseName, migrationsPath };
37
+ const config = {
38
+ instance: {
39
+ name: instanceName,
40
+ databases
41
+ }
42
+ };
48
43
  if (projectId) config.projectId = projectId;
49
44
  await init(config, CONFIG_FILE);
50
- console.log(`Configuration written to ${CONFIG_FILE}`);
51
45
  }
52
46
  ).command(
53
47
  "create <description ...>",
54
48
  "Create a new migration file",
55
49
  (yargs2) => {
56
- yargs2.positional("description", {
50
+ yargs2.option("database", {
51
+ alias: "d",
52
+ type: "string",
53
+ describe: "Database name"
54
+ }).positional("description", {
57
55
  type: "string",
58
56
  describe: "Description of the migration",
59
57
  demandOption: true
@@ -62,33 +60,157 @@ yargs(hideBin(process.argv)).scriptName("spanner-migrate").usage("$0 <command>")
62
60
  async (args) => {
63
61
  const config = await loadConfig();
64
62
  const fullDescription = args.description.join(" ");
65
- await create(config, fullDescription);
63
+ let databaseConfig;
64
+ if (args.database) {
65
+ databaseConfig = config.instance.databases.find(
66
+ (db) => db.name === args.database
67
+ );
68
+ if (!databaseConfig) {
69
+ throw new Error(`Unknown database name "${args.database}"`);
70
+ }
71
+ } else {
72
+ if (config.instance.databases.length === 1) {
73
+ databaseConfig = config.instance.databases[0];
74
+ } else {
75
+ databaseConfig = await select({
76
+ message: "Select database",
77
+ choices: config.instance.databases.map((dbConfig) => ({
78
+ name: dbConfig.name,
79
+ value: dbConfig
80
+ }))
81
+ });
82
+ }
83
+ }
84
+ if (!databaseConfig) throw new Error("No database config found");
85
+ await create(databaseConfig, fullDescription);
66
86
  console.log(
67
- `Migration file created: '${join(config.migrationsPath, args.description.join("_"))}.sql'`
87
+ `Migration file created: '${join(databaseConfig.migrationsPath, args.description.join("_"))}.sql'`
68
88
  );
69
89
  }
70
90
  ).command(
71
91
  "up",
72
92
  "Apply migrations",
73
93
  (yargs2) => {
74
- yargs2.option("max", {
94
+ yargs2.option("database", {
95
+ alias: "d",
96
+ type: "string",
97
+ describe: "Database name",
98
+ requiresArg: false
99
+ }).option("max", {
75
100
  alias: "m",
76
101
  type: "number",
77
- describe: "Maximum number of migrations to apply",
78
- default: 1e3
102
+ describe: "Maximum number of migrations to apply (requires --database)",
103
+ requiresArg: false
79
104
  });
80
105
  },
81
106
  async (args) => {
82
107
  const config = await loadConfig();
83
- await up(config, args.max);
108
+ if (args.max !== void 0) {
109
+ if (!args.database) {
110
+ throw new Error("The --max option requires a specified --database");
111
+ }
112
+ if (!Number.isInteger(args.max) || args.max <= 0) {
113
+ throw new Error("The --max option must be an integer greater than 0");
114
+ }
115
+ }
116
+ if (args.database) {
117
+ const databaseConfig = config.instance.databases.find(
118
+ (db) => db.name === args.database
119
+ );
120
+ if (!databaseConfig) {
121
+ throw new Error(`Unknown database name "${args.database}"`);
122
+ }
123
+ if (args.max !== void 0) {
124
+ await up(config, databaseConfig, args.max);
125
+ } else {
126
+ await up(config, databaseConfig);
127
+ }
128
+ } else {
129
+ await up(config);
130
+ }
84
131
  console.log("Migrations applied successfully.");
85
132
  }
86
- ).command("down", "Roll back the last applied migration", {}, async () => {
87
- const config = await loadConfig();
88
- await down(config);
89
- console.log("Last migration rolled back successfully.");
90
- }).command("status", "Show the migration status", {}, async () => {
91
- const config = await loadConfig();
92
- const migrationStatus = await status(config);
93
- console.log(migrationStatus);
94
- }).demandCommand().help().parse();
133
+ ).command(
134
+ "down",
135
+ "Roll back the last applied migration",
136
+ (yargs2) => {
137
+ yargs2.option("database", {
138
+ alias: "d",
139
+ type: "string",
140
+ describe: "Specify the database to roll back (required if multiple databases exist)"
141
+ });
142
+ },
143
+ async (args) => {
144
+ const config = await loadConfig();
145
+ let databaseConfig;
146
+ if (args.database) {
147
+ databaseConfig = config.instance.databases.find(
148
+ (dbConfig) => dbConfig.name === args.database
149
+ );
150
+ if (!databaseConfig) {
151
+ throw new Error(`Unknown database name "${args.database}"`);
152
+ }
153
+ } else if (config.instance.databases.length === 1) {
154
+ databaseConfig = config.instance.databases[0];
155
+ } else {
156
+ throw new Error(
157
+ "Multiple databases detected. Use --database to specify which one to roll back."
158
+ );
159
+ }
160
+ if (!databaseConfig) throw new Error("No database config found");
161
+ await down(config, databaseConfig);
162
+ console.log("Last migration rolled back successfully.");
163
+ }
164
+ ).command(
165
+ "status",
166
+ "Show the migration status",
167
+ (yargs2) => {
168
+ yargs2.option("database", {
169
+ alias: "d",
170
+ type: "string",
171
+ describe: "Specify a database to check status (optional, runs on all databases if omitted)"
172
+ });
173
+ },
174
+ async (args) => {
175
+ const config = await loadConfig();
176
+ let migrationStatus;
177
+ if (args.database) {
178
+ const databaseConfig = config.instance.databases.find(
179
+ (db) => db.name === args.database
180
+ );
181
+ if (!databaseConfig) {
182
+ throw new Error(`Unknown database name "${args.database}"`);
183
+ }
184
+ migrationStatus = await status(config, [databaseConfig]);
185
+ } else {
186
+ migrationStatus = await status(config);
187
+ }
188
+ console.log(migrationStatus);
189
+ }
190
+ ).demandCommand().help().parse();
191
+ async function loadConfig() {
192
+ try {
193
+ const configContent = await fs.readFile(CONFIG_FILE, "utf8");
194
+ return JSON.parse(configContent);
195
+ } catch {
196
+ console.error('Config file not found. Run "spanner-migrate init" first.');
197
+ process.exit(1);
198
+ }
199
+ }
200
+ var getDatabaseConfig = async (required) => {
201
+ const message = required ? "Enter Spanner database name" : "Enter another Spanner database name [Enter to continue]";
202
+ const name = await input({
203
+ message,
204
+ required
205
+ });
206
+ if (!name) return;
207
+ const migrationsPath = await input({
208
+ message: "Enter the path for your migrations",
209
+ required: true,
210
+ default: `./migrations/${name}`
211
+ });
212
+ return {
213
+ name,
214
+ migrationsPath
215
+ };
216
+ };
package/dist/index.d.mts CHANGED
@@ -1,14 +1,20 @@
1
- type Config = {
1
+ type DatabaseConfig = {
2
+ name: string;
2
3
  migrationsPath: string;
3
- instanceName: string;
4
- databaseName: string;
4
+ };
5
+ type InstanceConfig = {
6
+ name: string;
7
+ databases: DatabaseConfig[];
8
+ };
9
+ type Config = {
10
+ instance: InstanceConfig;
5
11
  projectId?: string;
6
12
  };
7
13
 
8
14
  declare const init: (config: Config, configPath: string) => Promise<void>;
9
- declare const create: (config: Config, description: string) => Promise<void>;
10
- declare const up: (config: Config, max?: number) => Promise<void>;
11
- declare const down: (config: Config) => Promise<void>;
12
- declare const status: (config: Config) => Promise<string>;
15
+ declare const create: (config: DatabaseConfig, description: string) => Promise<void>;
16
+ declare const up: (config: Config, database?: DatabaseConfig, max?: number) => Promise<void>;
17
+ declare const down: (config: Config, database: DatabaseConfig) => Promise<void>;
18
+ declare const status: (config: Config, databases?: DatabaseConfig[]) => Promise<string>;
13
19
 
14
20
  export { create, down, init, status, up };
package/dist/index.d.ts CHANGED
@@ -1,14 +1,20 @@
1
- type Config = {
1
+ type DatabaseConfig = {
2
+ name: string;
2
3
  migrationsPath: string;
3
- instanceName: string;
4
- databaseName: string;
4
+ };
5
+ type InstanceConfig = {
6
+ name: string;
7
+ databases: DatabaseConfig[];
8
+ };
9
+ type Config = {
10
+ instance: InstanceConfig;
5
11
  projectId?: string;
6
12
  };
7
13
 
8
14
  declare const init: (config: Config, configPath: string) => Promise<void>;
9
- declare const create: (config: Config, description: string) => Promise<void>;
10
- declare const up: (config: Config, max?: number) => Promise<void>;
11
- declare const down: (config: Config) => Promise<void>;
12
- declare const status: (config: Config) => Promise<string>;
15
+ declare const create: (config: DatabaseConfig, description: string) => Promise<void>;
16
+ declare const up: (config: Config, database?: DatabaseConfig, max?: number) => Promise<void>;
17
+ declare const down: (config: Config, database: DatabaseConfig) => Promise<void>;
18
+ declare const status: (config: Config, databases?: DatabaseConfig[]) => Promise<string>;
13
19
 
14
20
  export { create, down, init, status, up };
package/dist/index.js CHANGED
@@ -66,7 +66,7 @@ var applyDown = async (db) => {
66
66
  json: true
67
67
  };
68
68
  const [rows] = await db.run(req);
69
- const lastMigration = rows == null ? void 0 : rows[0];
69
+ const lastMigration = rows?.[0];
70
70
  if (!lastMigration) {
71
71
  throw new Error("No migrations found to roll back.");
72
72
  }
@@ -128,12 +128,12 @@ var SQL_CREATE_TABLE_MIGRATIONS = `
128
128
  down STRING(1024)
129
129
  ) PRIMARY KEY (id)
130
130
  `;
131
- var ensureMigrationTable = async (database) => {
132
- const [rows] = await database.run(SQL_SELECT_TABLE_MIGRATIONS);
131
+ var ensureMigrationTable = async (db) => {
132
+ const [rows] = await db.run(SQL_SELECT_TABLE_MIGRATIONS);
133
133
  if (rows.length) return;
134
134
  console.log("Creating migration table");
135
135
  try {
136
- await database.updateSchema(SQL_CREATE_TABLE_MIGRATIONS);
136
+ await db.updateSchema(SQL_CREATE_TABLE_MIGRATIONS);
137
137
  } catch (err) {
138
138
  console.error("Failed to create migrations table");
139
139
  throw err;
@@ -196,22 +196,17 @@ var getMigration = async (path, id) => {
196
196
  );
197
197
  }
198
198
  };
199
- var getDescription = (text) => {
200
- var _a, _b;
201
- return ((_b = (_a = text == null ? void 0 : text.match(/^--\s*Description:\s*(.+)$/m)) == null ? void 0 : _a[1]) == null ? void 0 : _b.trim()) || "";
202
- };
199
+ var getDescription = (text) => text?.match(/^--\s*Description:\s*(.+)$/m)?.[1]?.trim() || "";
203
200
  var getSql = (text, direction) => {
204
- var _a, _b;
205
201
  const rx = {
206
202
  up: /---- UP ----\n([\s\S]*?)\n---- DOWN ----/,
207
203
  down: /---- DOWN ----\n([\s\S]*)$/
208
204
  };
209
- return (_b = (_a = text == null ? void 0 : text.match(rx[direction])) == null ? void 0 : _a[1]) == null ? void 0 : _b.replace(/--.*$/gm, "").trim();
205
+ return text?.match(rx[direction])?.[1]?.replace(/--.*$/gm, "").trim();
210
206
  };
211
207
  var getNewMigrations = (applied, files) => {
212
208
  const sortedFiles = files.sort();
213
209
  for (let ix = 0; ix < applied.length; ix++) {
214
- console.log(sortedFiles[ix], applied[ix].id);
215
210
  if (sortedFiles[ix] !== applied[ix].id) {
216
211
  throw new Error(
217
212
  `Mismatch between applied migrations and files. Found '${sortedFiles[ix]}' but expected '${applied[ix].id}' at position ${ix}.`
@@ -219,7 +214,6 @@ var getNewMigrations = (applied, files) => {
219
214
  }
220
215
  }
221
216
  const newMigrations = sortedFiles.slice(applied.length);
222
- console.log(`Found ${newMigrations.length} new migrations.`);
223
217
  return newMigrations;
224
218
  };
225
219
  var createMigration = async (path, description) => {
@@ -270,42 +264,74 @@ var init = async (config, configPath) => {
270
264
  var create = async (config, description) => {
271
265
  await createMigration(config.migrationsPath, description);
272
266
  };
273
- var up = async (config, max = 1e3) => {
274
- const db = getDb(config);
275
- await ensureMigrationTable(db);
276
- const appliedMigrations = await getAppliedMigrations(db);
277
- const migrationFiles = await getMigrationFiles(config.migrationsPath);
278
- const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
279
- console.log(`Found ${newMigrations.length} new migrations.`);
280
- console.log(newMigrations.map((mig) => ` ${mig}`).join("\n"));
281
- for (const id of newMigrations.slice(0, max)) {
282
- const migration = await getMigration(config.migrationsPath, id);
283
- await applyUp(db, migration);
267
+ var up = async (config, database, max) => {
268
+ if (max && !database) {
269
+ throw new Error("Max number of migrations requires specifying a database");
270
+ }
271
+ const databases = database ? [database] : config.instance.databases;
272
+ for (const databaseConfig of databases) {
273
+ const path = {
274
+ projectId: config.projectId,
275
+ instanceName: config.instance.name,
276
+ databaseName: databaseConfig.name
277
+ };
278
+ const db = getDb(path);
279
+ await ensureMigrationTable(db);
280
+ const appliedMigrations = await getAppliedMigrations(db);
281
+ const migrationFiles = await getMigrationFiles(
282
+ databaseConfig.migrationsPath
283
+ );
284
+ const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
285
+ console.log(`Found ${newMigrations.length} new migrations.`);
286
+ console.log(newMigrations.map((mig) => ` ${mig}`).join("\n"));
287
+ const newMigrationsToApply = max ? newMigrations.slice(0, max) : newMigrations;
288
+ for (const id of newMigrationsToApply) {
289
+ const migration = await getMigration(databaseConfig.migrationsPath, id);
290
+ await applyUp(db, migration);
291
+ }
284
292
  }
285
293
  };
286
- var down = async (config) => {
287
- const db = getDb(config);
294
+ var down = async (config, database) => {
295
+ const path = {
296
+ projectId: config.projectId,
297
+ instanceName: config.instance.name,
298
+ databaseName: database.name
299
+ };
300
+ const db = getDb(path);
288
301
  await ensureMigrationTable(db);
289
302
  await applyDown(db);
290
303
  };
291
- var status = async (config) => {
292
- const db = getDb(config);
293
- await ensureMigrationTable(db);
294
- const appliedMigrations = await getAppliedMigrations(db);
295
- const migrationFiles = await getMigrationFiles(config.migrationsPath);
296
- const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
297
- return [
298
- "Migrations",
299
- "",
300
- "Applied",
301
- "--------------------------------------------------------------------------------",
302
- `${appliedMigrations.map((m) => m.id).join("\n")}
304
+ var status = async (config, databases) => {
305
+ const statuses = [];
306
+ for (const databaseConfig of databases || config.instance.databases) {
307
+ const path = {
308
+ projectId: config.projectId,
309
+ instanceName: config.instance.name,
310
+ databaseName: databaseConfig.name
311
+ };
312
+ const db = getDb(path);
313
+ await ensureMigrationTable(db);
314
+ const appliedMigrations = await getAppliedMigrations(db);
315
+ const migrationFiles = await getMigrationFiles(
316
+ databaseConfig.migrationsPath
317
+ );
318
+ const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
319
+ statuses.push(
320
+ [
321
+ `Migrations [${databaseConfig.name}]`,
322
+ "",
323
+ "Applied",
324
+ "--------------------------------------------------------------------------------",
325
+ `${appliedMigrations.map((m) => m.id).join("\n")}
303
326
  `,
304
- "New",
305
- "--------------------------------------------------------------------------------",
306
- `${newMigrations.join("\n")}
327
+ "New",
328
+ "--------------------------------------------------------------------------------",
329
+ `${newMigrations.join("\n")}
307
330
  `
308
- ].join("\n");
331
+ ].join("\n")
332
+ );
333
+ }
334
+ return statuses.join("\n\n");
309
335
  };
310
336
  // Annotate the CommonJS export names for ESM import in node:
311
337
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  init,
5
5
  status,
6
6
  up
7
- } from "./chunk-Z3ZLNDVO.mjs";
7
+ } from "./chunk-SZDH364K.mjs";
8
8
  export {
9
9
  create,
10
10
  down,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sebspark/spanner-migrate",
3
- "version": "0.2.0",
3
+ "version": "1.0.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -14,24 +14,29 @@
14
14
  "dev": "tsc --watch --noEmit",
15
15
  "lint": "biome check .",
16
16
  "test": "jest --config jest.config.ts --passWithNoTests --coverage",
17
- "test:e2e": "jest --config jest.e2e.config.ts",
17
+ "test:e2e": "jest --config jest.e2e.config.ts --runInBand",
18
18
  "typecheck": "tsc --noEmit "
19
19
  },
20
20
  "devDependencies": {
21
- "@google-cloud/spanner": "^7.17.1",
21
+ "@google-cloud/spanner": "7.18.1",
22
+ "@sebspark/cli-tester": "*",
22
23
  "@sebspark/spanner-mock": "*",
23
- "@types/jest": "^29.5.14",
24
- "@types/yargs": "^17.0.33",
25
- "jest": "^29.7.0",
26
- "testcontainers": "^10.16.0",
27
- "ts-jest": "^29.2.5",
24
+ "@types/jest": "29.5.14",
25
+ "@types/yargs": "17.0.33",
26
+ "jest": "29.7.0",
27
+ "testcontainers": "10.18.0",
28
+ "ts-jest": "29.2.6",
28
29
  "tsconfig": "*"
29
30
  },
30
31
  "peerDependencies": {
31
32
  "@google-cloud/spanner": "*"
32
33
  },
33
34
  "dependencies": {
34
- "@inquirer/input": "4.1.3",
35
+ "@inquirer/prompts": "7.3.2",
36
+ "@jest/globals": "29.7.0",
37
+ "@types/node": "20.17.17",
38
+ "typescript": "5.7.3",
39
+ "vitest": "3.0.7",
35
40
  "yargs": "17.7.2"
36
41
  }
37
42
  }