@sebspark/spanner-migrate 1.1.8 → 2.0.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/dist/cli.js CHANGED
@@ -1,387 +1,24 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") {
11
- for (let key of __getOwnPropNames(from))
12
- if (!__hasOwnProp.call(to, key) && key !== except)
13
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
- }
15
- return to;
16
- };
17
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
- // If the importer is in node compatibility mode or this is not an ESM
19
- // file that has been converted to a CommonJS file using a Babel-
20
- // compatible transform (i.e. "__esModule" has not been set), then set
21
- // "default" to the CommonJS "module.exports" for node compatibility.
22
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
- mod
24
- ));
25
-
26
- // src/cli.ts
27
- var import_promises2 = __toESM(require("fs/promises"));
28
- var import_node_path2 = require("path");
29
- var import_prompts = require("@inquirer/prompts");
30
- var import_yargs = __toESM(require("yargs"));
31
- var import_helpers = require("yargs/helpers");
32
-
33
- // src/index.ts
34
- var import_spanner = require("@google-cloud/spanner");
35
-
36
- // src/apply.ts
37
- var applyUp = async (db, migration) => {
38
- try {
39
- await runScript(db, migration.up);
40
- console.log(
41
- `Successfully applied migration script for: ${migration.description}`
42
- );
43
- const req = {
44
- sql: `
45
- INSERT INTO migrations (id, description, applied_at, up, down)
46
- VALUES (@id, @description, CURRENT_TIMESTAMP(), @up, @down)
47
- `,
48
- params: migration,
49
- json: true
50
- };
51
- await db.runTransactionAsync(async (transaction) => {
52
- await transaction.runUpdate(req);
53
- await transaction.commit();
54
- });
55
- console.log(`Migration recorded in the database: ${migration.id}`);
56
- } catch (error) {
57
- throw new Error(
58
- `Failed to apply migration ${migration.id}: ${error.message}`
59
- );
60
- }
61
- };
62
- var applyDown = async (db) => {
63
- const req = {
64
- sql: `
65
- SELECT id, description, up, down
66
- FROM migrations
67
- ORDER BY applied_at DESC
68
- LIMIT 1
69
- `,
70
- json: true
71
- };
72
- const [rows] = await db.run(req);
73
- const lastMigration = rows?.[0];
74
- if (!lastMigration) {
75
- throw new Error("No migrations found to roll back.");
76
- }
77
- try {
78
- await runScript(db, lastMigration.down);
79
- } catch (error) {
80
- throw new Error(
81
- `Failed to apply down script for migration ${lastMigration.id}: ${error.message}`
82
- );
83
- }
84
- await db.runTransactionAsync(async (transaction) => {
85
- await transaction.runUpdate({
86
- sql: `
87
- DELETE FROM migrations
88
- WHERE id = @id
89
- `,
90
- params: { id: lastMigration.id }
91
- });
92
- await transaction.commit();
93
- });
94
- console.log(
95
- `Successfully rolled back migration: ${lastMigration.description}`
96
- );
97
- };
98
- var runScript = async (db, script) => {
99
- const statements = script.split(";").filter(Boolean).map((stmt) => stmt.trim()).filter(Boolean);
100
- if (statements.length === 0) {
101
- throw new Error("No valid SQL statements found in the script.");
102
- }
103
- for (const statement of statements) {
104
- console.log(`Executing statement: ${statement}`);
105
- if (isSchemaChange(statement)) {
106
- await db.updateSchema(statement);
107
- } else {
108
- await db.runTransactionAsync(async (transaction) => {
109
- await transaction.runUpdate(statement);
110
- await transaction.commit();
111
- });
112
- }
113
- }
114
- };
115
- var isSchemaChange = (sql) => /^\s*(CREATE|ALTER|DROP|TRUNCATE)\b/i.test(sql);
116
-
117
- // src/db.ts
118
- var SQL_SELECT_TABLE_MIGRATIONS = `
119
- SELECT
120
- t.TABLE_NAME,
121
- c.COLUMN_NAME,
122
- c.SPANNER_TYPE
123
- FROM
124
- information_schema.TABLES t
125
- INNER JOIN
126
- information_schema.COLUMNS c
127
- ON t.TABLE_NAME = c.TABLE_NAME
128
- WHERE
129
- t.TABLE_NAME = 'migrations'
130
- `;
131
- var SQL_CREATE_TABLE_MIGRATIONS = `
132
- CREATE TABLE migrations (
133
- id STRING(128) NOT NULL,
134
- description STRING(256) NOT NULL,
135
- applied_at TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp = true),
136
- up STRING(MAX),
137
- down STRING(MAX)
138
- ) PRIMARY KEY (id)
139
- `;
140
- var ensureMigrationTable = async (db) => {
141
- const [rows] = await db.run({ sql: SQL_SELECT_TABLE_MIGRATIONS, json: true });
142
- if (rows.length === 0) {
143
- console.log("Creating migration table");
144
- try {
145
- await db.updateSchema(SQL_CREATE_TABLE_MIGRATIONS);
146
- } catch (err) {
147
- console.error("Failed to create migrations table");
148
- throw err;
149
- }
150
- } else {
151
- const typedRows = rows;
152
- const upType = typedRows.find((r) => r.COLUMN_NAME === "up");
153
- const downType = typedRows.find((r) => r.COLUMN_NAME === "down");
154
- const expectedType = "STRING(MAX)";
155
- if (upType?.SPANNER_TYPE !== expectedType) {
156
- try {
157
- console.log(
158
- `Updating 'up' column of migration table to ${expectedType}`
159
- );
160
- await db.updateSchema(
161
- `ALTER TABLE migrations ALTER COLUMN up ${expectedType}`
162
- );
163
- } catch (err) {
164
- console.error("Failed to update migrations table");
165
- throw err;
166
- }
167
- }
168
- if (downType?.SPANNER_TYPE !== expectedType) {
169
- try {
170
- console.log(
171
- `Updating 'down' column of migration table to ${expectedType}`
172
- );
173
- await db.updateSchema(
174
- `ALTER TABLE migrations ALTER COLUMN down ${expectedType}`
175
- );
176
- } catch (err) {
177
- console.error("Failed to update migrations table");
178
- throw err;
179
- }
180
- }
181
- }
182
- };
183
- var getAppliedMigrations = async (db) => {
184
- try {
185
- const req = {
186
- sql: `
187
- SELECT id, description, up, down, applied_at as appliedAt
188
- FROM migrations
189
- ORDER BY applied_at ASC
190
- `,
191
- json: true
192
- };
193
- const [rows] = await db.run(req);
194
- return rows;
195
- } catch (error) {
196
- throw new Error(
197
- `Failed to get applied migrations: ${error.message}`
198
- );
199
- }
200
- };
201
-
202
- // src/files.ts
203
- var import_promises = require("fs/promises");
204
- var import_node_path = require("path");
205
- var getMigrationFiles = async (path) => {
206
- try {
207
- const files = await (0, import_promises.readdir)(path);
208
- const migrationFileIds = files.filter((file) => file.endsWith(".sql")).map((file) => file.replace(/\.sql$/, ""));
209
- return migrationFileIds;
210
- } catch (error) {
211
- throw new Error(
212
- `Failed to get migration files: ${error.message}`
213
- );
214
- }
215
- };
216
- var getMigration = async (path, id) => {
217
- try {
218
- const filePath = (0, import_node_path.resolve)(process.cwd(), (0, import_node_path.join)(path, `${id}.sql`));
219
- try {
220
- await (0, import_promises.access)(filePath);
221
- } catch {
222
- throw new Error(`Migration file not found: ${filePath}`);
223
- }
224
- const migrationText = await (0, import_promises.readFile)(filePath, "utf8");
225
- const up2 = getSql(migrationText, "up");
226
- const down2 = getSql(migrationText, "down");
227
- const description = getDescription(migrationText);
228
- if (!up2 || !down2) {
229
- throw new Error(
230
- `Migration file ${filePath} does not export required scripts (up, down).`
231
- );
232
- }
233
- return { id, description, up: up2, down: down2 };
234
- } catch (error) {
235
- throw new Error(
236
- `Failed to get migration ${id}: ${error.message}`
237
- );
238
- }
239
- };
240
- var getDescription = (text) => text?.match(/^--\s*Description:\s*(.+)$/m)?.[1]?.trim() || "";
241
- var getSql = (text, direction) => {
242
- const rx = {
243
- up: /---- UP ----\n([\s\S]*?)\n---- DOWN ----/,
244
- down: /---- DOWN ----\n([\s\S]*)$/
245
- };
246
- return text?.match(rx[direction])?.[1]?.replace(/--.*$/gm, "").trim();
247
- };
248
- var getNewMigrations = (applied, files) => {
249
- const sortedFiles = files.sort();
250
- for (let ix = 0; ix < applied.length; ix++) {
251
- if (sortedFiles[ix] !== applied[ix].id) {
252
- throw new Error(
253
- `Mismatch between applied migrations and files. Found '${sortedFiles[ix]}' but expected '${applied[ix].id}' at position ${ix}.`
254
- );
255
- }
256
- }
257
- const newMigrations = sortedFiles.slice(applied.length);
258
- return newMigrations;
259
- };
260
- var createMigration = async (path, description) => {
261
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
262
- const compactTimestamp = timestamp.replace(/[-:.TZ]/g, "");
263
- const parsedDescription = description.replace(/\s+/g, "_").toLowerCase();
264
- const filename = `${compactTimestamp}_${parsedDescription}.sql`;
265
- const filePath = (0, import_node_path.join)(path, filename);
266
- const template = `-- Created: ${timestamp}
267
- -- Description: ${description}
268
-
269
- ---- UP ----
270
-
271
-
272
-
273
- ---- DOWN ----
274
-
275
-
276
- `;
277
- try {
278
- await (0, import_promises.mkdir)(path, { recursive: true });
279
- await (0, import_promises.writeFile)(filePath, template.trim(), "utf8");
280
- console.log(`Migration created: ${filePath}`);
281
- } catch (error) {
282
- throw new Error(`Error creating migration: ${error.message}`);
283
- }
284
- };
285
- var writeConfig = async (path, config) => {
286
- try {
287
- const configContent = JSON.stringify(config, null, 2);
288
- await (0, import_promises.writeFile)(path, configContent, "utf8");
289
- console.log(`Configuration written to ${path}`);
290
- } catch (error) {
291
- throw new Error(
292
- `Error writing configuration to ${path}: ${error.message}`
293
- );
294
- }
295
- };
296
-
297
- // src/index.ts
298
- var getDb = ({ projectId, databaseName, instanceName }) => {
299
- const spanner = projectId ? new import_spanner.Spanner({ projectId }) : new import_spanner.Spanner();
300
- return spanner.instance(instanceName).database(databaseName);
301
- };
302
- var init = async (config, configPath) => {
303
- await writeConfig(configPath, config);
304
- };
305
- var create = async (config, description) => {
306
- await createMigration(config.migrationsPath, description);
307
- };
308
- var up = async (config, database, max) => {
309
- if (max && !database) {
310
- throw new Error("Max number of migrations requires specifying a database");
311
- }
312
- const databases = database ? [database] : config.instance.databases;
313
- for (const databaseConfig of databases) {
314
- const path = {
315
- projectId: config.projectId,
316
- instanceName: config.instance.name,
317
- databaseName: databaseConfig.name
318
- };
319
- const db = getDb(path);
320
- await ensureMigrationTable(db);
321
- const appliedMigrations = await getAppliedMigrations(db);
322
- const migrationFiles = await getMigrationFiles(
323
- databaseConfig.migrationsPath
324
- );
325
- const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
326
- console.log(`Found ${newMigrations.length} new migrations.`);
327
- console.log(newMigrations.map((mig) => ` ${mig}`).join("\n"));
328
- const newMigrationsToApply = max ? newMigrations.slice(0, max) : newMigrations;
329
- for (const id of newMigrationsToApply) {
330
- const migration = await getMigration(databaseConfig.migrationsPath, id);
331
- await applyUp(db, migration);
332
- }
333
- }
334
- };
335
- var down = async (config, database) => {
336
- const path = {
337
- projectId: config.projectId,
338
- instanceName: config.instance.name,
339
- databaseName: database.name
340
- };
341
- const db = getDb(path);
342
- await ensureMigrationTable(db);
343
- await applyDown(db);
344
- };
345
- var status = async (config, databases) => {
346
- const statuses = [];
347
- for (const databaseConfig of databases || config.instance.databases) {
348
- const path = {
349
- projectId: config.projectId,
350
- instanceName: config.instance.name,
351
- databaseName: databaseConfig.name
352
- };
353
- const db = getDb(path);
354
- await ensureMigrationTable(db);
355
- const appliedMigrations = await getAppliedMigrations(db);
356
- const migrationFiles = await getMigrationFiles(
357
- databaseConfig.migrationsPath
358
- );
359
- const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
360
- statuses.push(
361
- [
362
- `Migrations [${databaseConfig.name}]`,
363
- "",
364
- "Applied",
365
- "--------------------------------------------------------------------------------",
366
- `${appliedMigrations.map((m) => m.id).join("\n")}
367
- `,
368
- "New",
369
- "--------------------------------------------------------------------------------",
370
- `${newMigrations.join("\n")}
371
- `
372
- ].join("\n")
373
- );
374
- }
375
- return statuses.join("\n\n");
376
- };
2
+ import {
3
+ create,
4
+ down,
5
+ init,
6
+ status,
7
+ up
8
+ } from "./chunk-XP6DPC5R.js";
377
9
 
378
10
  // src/cli.ts
11
+ import fs from "fs/promises";
12
+ import { join } from "path";
13
+ import { input, select } from "@inquirer/prompts";
14
+ import yargs from "yargs";
15
+ import { hideBin } from "yargs/helpers";
379
16
  var CONFIG_FILE = "./.spanner-migrate.config.json";
380
- (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).scriptName("spanner-migrate").usage("$0 <command>").command(
17
+ yargs(hideBin(process.argv)).scriptName("spanner-migrate").usage("$0 <command>").command(
381
18
  "init",
382
19
  "Initialize a .spanner-migrate.config.json file",
383
20
  async () => {
384
- const instanceName = await (0, import_prompts.input)({
21
+ const instanceName = await input({
385
22
  message: "Enter Spanner instance name",
386
23
  required: true
387
24
  });
@@ -393,7 +30,7 @@ var CONFIG_FILE = "./.spanner-migrate.config.json";
393
30
  if (!dbConfig) break;
394
31
  databases.push(dbConfig);
395
32
  }
396
- const projectId = await (0, import_prompts.input)({
33
+ const projectId = await input({
397
34
  message: "Enter Google Cloud project name",
398
35
  required: false
399
36
  });
@@ -435,7 +72,7 @@ var CONFIG_FILE = "./.spanner-migrate.config.json";
435
72
  if (config.instance.databases.length === 1) {
436
73
  databaseConfig = config.instance.databases[0];
437
74
  } else {
438
- databaseConfig = await (0, import_prompts.select)({
75
+ databaseConfig = await select({
439
76
  message: "Select database",
440
77
  choices: config.instance.databases.map((dbConfig) => ({
441
78
  name: dbConfig.name,
@@ -447,7 +84,7 @@ var CONFIG_FILE = "./.spanner-migrate.config.json";
447
84
  if (!databaseConfig) throw new Error("No database config found");
448
85
  await create(databaseConfig, fullDescription);
449
86
  console.log(
450
- `Migration file created: '${(0, import_node_path2.join)(databaseConfig.migrationsPath, args.description.join("_"))}.sql'`
87
+ `Migration file created: '${join(databaseConfig.migrationsPath, args.description.join("_"))}.sql'`
451
88
  );
452
89
  }
453
90
  ).command(
@@ -553,7 +190,7 @@ var CONFIG_FILE = "./.spanner-migrate.config.json";
553
190
  ).demandCommand().help().parse();
554
191
  async function loadConfig() {
555
192
  try {
556
- const configContent = await import_promises2.default.readFile(CONFIG_FILE, "utf8");
193
+ const configContent = await fs.readFile(CONFIG_FILE, "utf8");
557
194
  return JSON.parse(configContent);
558
195
  } catch {
559
196
  console.error('Config file not found. Run "spanner-migrate init" first.');
@@ -562,12 +199,12 @@ async function loadConfig() {
562
199
  }
563
200
  var getDatabaseConfig = async (required) => {
564
201
  const message = required ? "Enter Spanner database name" : "Enter another Spanner database name [Enter to continue]";
565
- const name = await (0, import_prompts.input)({
202
+ const name = await input({
566
203
  message,
567
204
  required
568
205
  });
569
206
  if (!name) return;
570
- const migrationsPath = await (0, import_prompts.input)({
207
+ const migrationsPath = await input({
571
208
  message: "Enter the path for your migrations",
572
209
  required: true,
573
210
  default: `./migrations/${name}`
package/dist/index.js CHANGED
@@ -1,380 +1,14 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- create: () => create,
24
- down: () => down,
25
- init: () => init,
26
- status: () => status,
27
- up: () => up
28
- });
29
- module.exports = __toCommonJS(index_exports);
30
- var import_spanner = require("@google-cloud/spanner");
31
-
32
- // src/apply.ts
33
- var applyUp = async (db, migration) => {
34
- try {
35
- await runScript(db, migration.up);
36
- console.log(
37
- `Successfully applied migration script for: ${migration.description}`
38
- );
39
- const req = {
40
- sql: `
41
- INSERT INTO migrations (id, description, applied_at, up, down)
42
- VALUES (@id, @description, CURRENT_TIMESTAMP(), @up, @down)
43
- `,
44
- params: migration,
45
- json: true
46
- };
47
- await db.runTransactionAsync(async (transaction) => {
48
- await transaction.runUpdate(req);
49
- await transaction.commit();
50
- });
51
- console.log(`Migration recorded in the database: ${migration.id}`);
52
- } catch (error) {
53
- throw new Error(
54
- `Failed to apply migration ${migration.id}: ${error.message}`
55
- );
56
- }
57
- };
58
- var applyDown = async (db) => {
59
- const req = {
60
- sql: `
61
- SELECT id, description, up, down
62
- FROM migrations
63
- ORDER BY applied_at DESC
64
- LIMIT 1
65
- `,
66
- json: true
67
- };
68
- const [rows] = await db.run(req);
69
- const lastMigration = rows?.[0];
70
- if (!lastMigration) {
71
- throw new Error("No migrations found to roll back.");
72
- }
73
- try {
74
- await runScript(db, lastMigration.down);
75
- } catch (error) {
76
- throw new Error(
77
- `Failed to apply down script for migration ${lastMigration.id}: ${error.message}`
78
- );
79
- }
80
- await db.runTransactionAsync(async (transaction) => {
81
- await transaction.runUpdate({
82
- sql: `
83
- DELETE FROM migrations
84
- WHERE id = @id
85
- `,
86
- params: { id: lastMigration.id }
87
- });
88
- await transaction.commit();
89
- });
90
- console.log(
91
- `Successfully rolled back migration: ${lastMigration.description}`
92
- );
93
- };
94
- var runScript = async (db, script) => {
95
- const statements = script.split(";").filter(Boolean).map((stmt) => stmt.trim()).filter(Boolean);
96
- if (statements.length === 0) {
97
- throw new Error("No valid SQL statements found in the script.");
98
- }
99
- for (const statement of statements) {
100
- console.log(`Executing statement: ${statement}`);
101
- if (isSchemaChange(statement)) {
102
- await db.updateSchema(statement);
103
- } else {
104
- await db.runTransactionAsync(async (transaction) => {
105
- await transaction.runUpdate(statement);
106
- await transaction.commit();
107
- });
108
- }
109
- }
110
- };
111
- var isSchemaChange = (sql) => /^\s*(CREATE|ALTER|DROP|TRUNCATE)\b/i.test(sql);
112
-
113
- // src/db.ts
114
- var SQL_SELECT_TABLE_MIGRATIONS = `
115
- SELECT
116
- t.TABLE_NAME,
117
- c.COLUMN_NAME,
118
- c.SPANNER_TYPE
119
- FROM
120
- information_schema.TABLES t
121
- INNER JOIN
122
- information_schema.COLUMNS c
123
- ON t.TABLE_NAME = c.TABLE_NAME
124
- WHERE
125
- t.TABLE_NAME = 'migrations'
126
- `;
127
- var SQL_CREATE_TABLE_MIGRATIONS = `
128
- CREATE TABLE migrations (
129
- id STRING(128) NOT NULL,
130
- description STRING(256) NOT NULL,
131
- applied_at TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp = true),
132
- up STRING(MAX),
133
- down STRING(MAX)
134
- ) PRIMARY KEY (id)
135
- `;
136
- var ensureMigrationTable = async (db) => {
137
- const [rows] = await db.run({ sql: SQL_SELECT_TABLE_MIGRATIONS, json: true });
138
- if (rows.length === 0) {
139
- console.log("Creating migration table");
140
- try {
141
- await db.updateSchema(SQL_CREATE_TABLE_MIGRATIONS);
142
- } catch (err) {
143
- console.error("Failed to create migrations table");
144
- throw err;
145
- }
146
- } else {
147
- const typedRows = rows;
148
- const upType = typedRows.find((r) => r.COLUMN_NAME === "up");
149
- const downType = typedRows.find((r) => r.COLUMN_NAME === "down");
150
- const expectedType = "STRING(MAX)";
151
- if (upType?.SPANNER_TYPE !== expectedType) {
152
- try {
153
- console.log(
154
- `Updating 'up' column of migration table to ${expectedType}`
155
- );
156
- await db.updateSchema(
157
- `ALTER TABLE migrations ALTER COLUMN up ${expectedType}`
158
- );
159
- } catch (err) {
160
- console.error("Failed to update migrations table");
161
- throw err;
162
- }
163
- }
164
- if (downType?.SPANNER_TYPE !== expectedType) {
165
- try {
166
- console.log(
167
- `Updating 'down' column of migration table to ${expectedType}`
168
- );
169
- await db.updateSchema(
170
- `ALTER TABLE migrations ALTER COLUMN down ${expectedType}`
171
- );
172
- } catch (err) {
173
- console.error("Failed to update migrations table");
174
- throw err;
175
- }
176
- }
177
- }
178
- };
179
- var getAppliedMigrations = async (db) => {
180
- try {
181
- const req = {
182
- sql: `
183
- SELECT id, description, up, down, applied_at as appliedAt
184
- FROM migrations
185
- ORDER BY applied_at ASC
186
- `,
187
- json: true
188
- };
189
- const [rows] = await db.run(req);
190
- return rows;
191
- } catch (error) {
192
- throw new Error(
193
- `Failed to get applied migrations: ${error.message}`
194
- );
195
- }
196
- };
197
-
198
- // src/files.ts
199
- var import_promises = require("fs/promises");
200
- var import_node_path = require("path");
201
- var getMigrationFiles = async (path) => {
202
- try {
203
- const files = await (0, import_promises.readdir)(path);
204
- const migrationFileIds = files.filter((file) => file.endsWith(".sql")).map((file) => file.replace(/\.sql$/, ""));
205
- return migrationFileIds;
206
- } catch (error) {
207
- throw new Error(
208
- `Failed to get migration files: ${error.message}`
209
- );
210
- }
211
- };
212
- var getMigration = async (path, id) => {
213
- try {
214
- const filePath = (0, import_node_path.resolve)(process.cwd(), (0, import_node_path.join)(path, `${id}.sql`));
215
- try {
216
- await (0, import_promises.access)(filePath);
217
- } catch {
218
- throw new Error(`Migration file not found: ${filePath}`);
219
- }
220
- const migrationText = await (0, import_promises.readFile)(filePath, "utf8");
221
- const up2 = getSql(migrationText, "up");
222
- const down2 = getSql(migrationText, "down");
223
- const description = getDescription(migrationText);
224
- if (!up2 || !down2) {
225
- throw new Error(
226
- `Migration file ${filePath} does not export required scripts (up, down).`
227
- );
228
- }
229
- return { id, description, up: up2, down: down2 };
230
- } catch (error) {
231
- throw new Error(
232
- `Failed to get migration ${id}: ${error.message}`
233
- );
234
- }
235
- };
236
- var getDescription = (text) => text?.match(/^--\s*Description:\s*(.+)$/m)?.[1]?.trim() || "";
237
- var getSql = (text, direction) => {
238
- const rx = {
239
- up: /---- UP ----\n([\s\S]*?)\n---- DOWN ----/,
240
- down: /---- DOWN ----\n([\s\S]*)$/
241
- };
242
- return text?.match(rx[direction])?.[1]?.replace(/--.*$/gm, "").trim();
243
- };
244
- var getNewMigrations = (applied, files) => {
245
- const sortedFiles = files.sort();
246
- for (let ix = 0; ix < applied.length; ix++) {
247
- if (sortedFiles[ix] !== applied[ix].id) {
248
- throw new Error(
249
- `Mismatch between applied migrations and files. Found '${sortedFiles[ix]}' but expected '${applied[ix].id}' at position ${ix}.`
250
- );
251
- }
252
- }
253
- const newMigrations = sortedFiles.slice(applied.length);
254
- return newMigrations;
255
- };
256
- var createMigration = async (path, description) => {
257
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
258
- const compactTimestamp = timestamp.replace(/[-:.TZ]/g, "");
259
- const parsedDescription = description.replace(/\s+/g, "_").toLowerCase();
260
- const filename = `${compactTimestamp}_${parsedDescription}.sql`;
261
- const filePath = (0, import_node_path.join)(path, filename);
262
- const template = `-- Created: ${timestamp}
263
- -- Description: ${description}
264
-
265
- ---- UP ----
266
-
267
-
268
-
269
- ---- DOWN ----
270
-
271
-
272
- `;
273
- try {
274
- await (0, import_promises.mkdir)(path, { recursive: true });
275
- await (0, import_promises.writeFile)(filePath, template.trim(), "utf8");
276
- console.log(`Migration created: ${filePath}`);
277
- } catch (error) {
278
- throw new Error(`Error creating migration: ${error.message}`);
279
- }
280
- };
281
- var writeConfig = async (path, config) => {
282
- try {
283
- const configContent = JSON.stringify(config, null, 2);
284
- await (0, import_promises.writeFile)(path, configContent, "utf8");
285
- console.log(`Configuration written to ${path}`);
286
- } catch (error) {
287
- throw new Error(
288
- `Error writing configuration to ${path}: ${error.message}`
289
- );
290
- }
291
- };
292
-
293
- // src/index.ts
294
- var getDb = ({ projectId, databaseName, instanceName }) => {
295
- const spanner = projectId ? new import_spanner.Spanner({ projectId }) : new import_spanner.Spanner();
296
- return spanner.instance(instanceName).database(databaseName);
297
- };
298
- var init = async (config, configPath) => {
299
- await writeConfig(configPath, config);
300
- };
301
- var create = async (config, description) => {
302
- await createMigration(config.migrationsPath, description);
303
- };
304
- var up = async (config, database, max) => {
305
- if (max && !database) {
306
- throw new Error("Max number of migrations requires specifying a database");
307
- }
308
- const databases = database ? [database] : config.instance.databases;
309
- for (const databaseConfig of databases) {
310
- const path = {
311
- projectId: config.projectId,
312
- instanceName: config.instance.name,
313
- databaseName: databaseConfig.name
314
- };
315
- const db = getDb(path);
316
- await ensureMigrationTable(db);
317
- const appliedMigrations = await getAppliedMigrations(db);
318
- const migrationFiles = await getMigrationFiles(
319
- databaseConfig.migrationsPath
320
- );
321
- const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
322
- console.log(`Found ${newMigrations.length} new migrations.`);
323
- console.log(newMigrations.map((mig) => ` ${mig}`).join("\n"));
324
- const newMigrationsToApply = max ? newMigrations.slice(0, max) : newMigrations;
325
- for (const id of newMigrationsToApply) {
326
- const migration = await getMigration(databaseConfig.migrationsPath, id);
327
- await applyUp(db, migration);
328
- }
329
- }
330
- };
331
- var down = async (config, database) => {
332
- const path = {
333
- projectId: config.projectId,
334
- instanceName: config.instance.name,
335
- databaseName: database.name
336
- };
337
- const db = getDb(path);
338
- await ensureMigrationTable(db);
339
- await applyDown(db);
340
- };
341
- var status = async (config, databases) => {
342
- const statuses = [];
343
- for (const databaseConfig of databases || config.instance.databases) {
344
- const path = {
345
- projectId: config.projectId,
346
- instanceName: config.instance.name,
347
- databaseName: databaseConfig.name
348
- };
349
- const db = getDb(path);
350
- await ensureMigrationTable(db);
351
- const appliedMigrations = await getAppliedMigrations(db);
352
- const migrationFiles = await getMigrationFiles(
353
- databaseConfig.migrationsPath
354
- );
355
- const newMigrations = getNewMigrations(appliedMigrations, migrationFiles);
356
- statuses.push(
357
- [
358
- `Migrations [${databaseConfig.name}]`,
359
- "",
360
- "Applied",
361
- "--------------------------------------------------------------------------------",
362
- `${appliedMigrations.map((m) => m.id).join("\n")}
363
- `,
364
- "New",
365
- "--------------------------------------------------------------------------------",
366
- `${newMigrations.join("\n")}
367
- `
368
- ].join("\n")
369
- );
370
- }
371
- return statuses.join("\n\n");
372
- };
373
- // Annotate the CommonJS export names for ESM import in node:
374
- 0 && (module.exports = {
1
+ import {
375
2
  create,
376
3
  down,
377
4
  init,
378
5
  status,
379
6
  up
380
- });
7
+ } from "./chunk-XP6DPC5R.js";
8
+ export {
9
+ create,
10
+ down,
11
+ init,
12
+ status,
13
+ up
14
+ };
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@sebspark/spanner-migrate",
3
- "version": "1.1.8",
3
+ "version": "2.0.1",
4
4
  "license": "Apache-2.0",
5
+ "type": "module",
5
6
  "main": "dist/index.js",
6
- "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
8
  "files": [
9
9
  "dist"
10
10
  ],
11
11
  "bin": "./dist/cli.js",
12
12
  "scripts": {
13
- "build": "tsup-node src/index.ts src/cli.ts --format esm,cjs --dts",
13
+ "build": "tsup-node src/index.ts src/cli.ts --format esm --target node22 --dts",
14
14
  "dev": "tsc --watch --noEmit",
15
15
  "lint": "biome check .",
16
16
  "test": "vitest run --passWithNoTests --coverage",
package/dist/cli.d.mts DELETED
@@ -1 +0,0 @@
1
- #!/usr/bin/env node
package/dist/cli.mjs DELETED
@@ -1,216 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- create,
4
- down,
5
- init,
6
- status,
7
- up
8
- } from "./chunk-YL5IK6VK.mjs";
9
-
10
- // src/cli.ts
11
- import fs from "fs/promises";
12
- import { join } from "path";
13
- import { input, select } from "@inquirer/prompts";
14
- import yargs from "yargs";
15
- import { hideBin } from "yargs/helpers";
16
- var CONFIG_FILE = "./.spanner-migrate.config.json";
17
- yargs(hideBin(process.argv)).scriptName("spanner-migrate").usage("$0 <command>").command(
18
- "init",
19
- "Initialize a .spanner-migrate.config.json file",
20
- async () => {
21
- const instanceName = await input({
22
- message: "Enter Spanner instance name",
23
- required: true
24
- });
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
- }
33
- const projectId = await input({
34
- message: "Enter Google Cloud project name",
35
- required: false
36
- });
37
- const config = {
38
- instance: {
39
- name: instanceName,
40
- databases
41
- }
42
- };
43
- if (projectId) config.projectId = projectId;
44
- await init(config, CONFIG_FILE);
45
- }
46
- ).command(
47
- "create <description ...>",
48
- "Create a new migration file",
49
- (yargs2) => {
50
- yargs2.option("database", {
51
- alias: "d",
52
- type: "string",
53
- describe: "Database name"
54
- }).positional("description", {
55
- type: "string",
56
- describe: "Description of the migration",
57
- demandOption: true
58
- });
59
- },
60
- async (args) => {
61
- const config = await loadConfig();
62
- const fullDescription = args.description.join(" ");
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);
86
- console.log(
87
- `Migration file created: '${join(databaseConfig.migrationsPath, args.description.join("_"))}.sql'`
88
- );
89
- }
90
- ).command(
91
- "up",
92
- "Apply migrations",
93
- (yargs2) => {
94
- yargs2.option("database", {
95
- alias: "d",
96
- type: "string",
97
- describe: "Database name",
98
- requiresArg: false
99
- }).option("max", {
100
- alias: "m",
101
- type: "number",
102
- describe: "Maximum number of migrations to apply (requires --database)",
103
- requiresArg: false
104
- });
105
- },
106
- async (args) => {
107
- const config = await loadConfig();
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
- }
131
- console.log("Migrations applied successfully.");
132
- }
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 DELETED
@@ -1,20 +0,0 @@
1
- type DatabaseConfig = {
2
- name: string;
3
- migrationsPath: string;
4
- };
5
- type InstanceConfig = {
6
- name: string;
7
- databases: DatabaseConfig[];
8
- };
9
- type Config = {
10
- instance: InstanceConfig;
11
- projectId?: string;
12
- };
13
-
14
- declare const init: (config: Config, configPath: string) => Promise<void>;
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>;
19
-
20
- export { create, down, init, status, up };
package/dist/index.mjs DELETED
@@ -1,14 +0,0 @@
1
- import {
2
- create,
3
- down,
4
- init,
5
- status,
6
- up
7
- } from "./chunk-YL5IK6VK.mjs";
8
- export {
9
- create,
10
- down,
11
- init,
12
- status,
13
- up
14
- };
File without changes