@noego/proper 0.2.1 → 0.3.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/bin/index.mjs CHANGED
@@ -41,9 +41,6 @@ var __async = (__this, __arguments, generator) => {
41
41
  // framework/MigrationRunner.ts
42
42
  import fs6 from "fs";
43
43
 
44
- // framework/MigrationDirectoryReader.ts
45
- import fs2 from "fs";
46
-
47
44
  // framework/errors.ts
48
45
  var MigrationError = class _MigrationError extends Error {
49
46
  constructor(message) {
@@ -108,6 +105,16 @@ var DatabaseConnectionError = class _DatabaseConnectionError extends MigrationEr
108
105
  return new _DatabaseConnectionError(`Authentication failed for ${dbType} database`);
109
106
  }
110
107
  };
108
+ var MigrationStampError = class _MigrationStampError extends MigrationError {
109
+ constructor(migrationKey) {
110
+ super(
111
+ `Migration '${migrationKey}' cannot be migrated: its timestamp ends in "000", so it does not look like it was generated by 'proper create'. Recreate it with 'proper create <name>' and move the SQL into the generated files.`
112
+ );
113
+ this.migrationKey = migrationKey;
114
+ this.name = "MigrationStampError";
115
+ Object.setPrototypeOf(this, _MigrationStampError.prototype);
116
+ }
117
+ };
111
118
  var MigrationExecutionError = class _MigrationExecutionError extends MigrationError {
112
119
  constructor(message, migrationName, sql, originalError) {
113
120
  let fullMessage = `Migration Execution Error${migrationName ? ` in '${migrationName}'` : ""}: ${message}`;
@@ -236,6 +243,71 @@ var CLIError = class _CLIError extends MigrationError {
236
243
  }
237
244
  };
238
245
 
246
+ // framework/MigrationManifest.ts
247
+ import fs from "fs";
248
+ import path from "path";
249
+ var MIGRATION_FILE_REGEX = /(?:\.(mysql|sqlite|pg))?\.(up|down)\.(sql|js)$/i;
250
+ function isMigrationFile(fileName) {
251
+ return MIGRATION_FILE_REGEX.test(fileName);
252
+ }
253
+ function canonicalMigrationKey(value) {
254
+ return value.replace(MIGRATION_FILE_REGEX, "").toLowerCase();
255
+ }
256
+ function mintMigrationStamp(now = Date.now()) {
257
+ return now % 10 === 0 ? now + 1 : now;
258
+ }
259
+ function isHandStampedKey(key) {
260
+ const stamp = key.split("_")[0];
261
+ return /^\d+$/.test(stamp) && /000$/.test(stamp);
262
+ }
263
+ function resolveMigrationFile(directory, baseName, direction, dialect) {
264
+ const dialectExt = dialect === "sql" ? "mysql" : dialect;
265
+ const dialectFile = path.join(directory, `${baseName}.${dialectExt}.${direction}.sql`);
266
+ if (fs.existsSync(dialectFile)) return dialectFile;
267
+ const genericFile = path.join(directory, `${baseName}.${direction}.sql`);
268
+ if (fs.existsSync(genericFile)) return genericFile;
269
+ return null;
270
+ }
271
+ function loadMigrationManifest(directory, dialect) {
272
+ const manifest = /* @__PURE__ */ new Map();
273
+ if (!fs.existsSync(directory)) return manifest;
274
+ const files = fs.readdirSync(directory, { withFileTypes: true }).filter((f) => f.isFile()).map((f) => f.name).filter(isMigrationFile);
275
+ const uniqueKeys = /* @__PURE__ */ new Set();
276
+ files.forEach((file) => uniqueKeys.add(canonicalMigrationKey(file)));
277
+ uniqueKeys.forEach((key) => {
278
+ manifest.set(key, {
279
+ key,
280
+ upFile: resolveMigrationFile(directory, key, "up", dialect),
281
+ downFile: resolveMigrationFile(directory, key, "down", dialect)
282
+ });
283
+ });
284
+ return manifest;
285
+ }
286
+
287
+ // framework/MigrationConfig.ts
288
+ function validateLegacyMigrationKeys(value) {
289
+ if (value === void 0) {
290
+ return;
291
+ }
292
+ if (!Array.isArray(value) || value.some((key) => !isExactCanonicalMigrationKey(key))) {
293
+ throw new ConfigurationError(
294
+ "legacy_migration_keys must be an array of nonempty exact canonical migration keys; filenames, paths, wildcards, and nonstrings are not allowed"
295
+ );
296
+ }
297
+ }
298
+ function isExactCanonicalMigrationKey(value) {
299
+ if (typeof value !== "string" || value.length === 0 || value !== value.trim()) {
300
+ return false;
301
+ }
302
+ if (value.includes("/") || value.includes("\\") || ["*", "?", "[", "]", "{", "}"].some((token) => value.includes(token))) {
303
+ return false;
304
+ }
305
+ return !isMigrationFile(value) && canonicalMigrationKey(value) === value;
306
+ }
307
+
308
+ // framework/MigrationDirectoryReader.ts
309
+ import fs2 from "fs";
310
+
239
311
  // framework/MigrationNode.ts
240
312
  var MigrationNode = class {
241
313
  constructor(name) {
@@ -367,40 +439,6 @@ var SqlMigrationBuilder = class {
367
439
  }
368
440
  };
369
441
 
370
- // framework/MigrationManifest.ts
371
- import fs from "fs";
372
- import path from "path";
373
- var MIGRATION_FILE_REGEX = /(?:\.(mysql|sqlite|pg))?\.(up|down)\.(sql|js)$/i;
374
- function isMigrationFile(fileName) {
375
- return MIGRATION_FILE_REGEX.test(fileName);
376
- }
377
- function canonicalMigrationKey(value) {
378
- return value.replace(MIGRATION_FILE_REGEX, "").toLowerCase();
379
- }
380
- function resolveMigrationFile(directory, baseName, direction, dialect) {
381
- const dialectExt = dialect === "sql" ? "mysql" : dialect;
382
- const dialectFile = path.join(directory, `${baseName}.${dialectExt}.${direction}.sql`);
383
- if (fs.existsSync(dialectFile)) return dialectFile;
384
- const genericFile = path.join(directory, `${baseName}.${direction}.sql`);
385
- if (fs.existsSync(genericFile)) return genericFile;
386
- return null;
387
- }
388
- function loadMigrationManifest(directory, dialect) {
389
- const manifest = /* @__PURE__ */ new Map();
390
- if (!fs.existsSync(directory)) return manifest;
391
- const files = fs.readdirSync(directory, { withFileTypes: true }).filter((f) => f.isFile()).map((f) => f.name).filter(isMigrationFile);
392
- const uniqueKeys = /* @__PURE__ */ new Set();
393
- files.forEach((file) => uniqueKeys.add(canonicalMigrationKey(file)));
394
- uniqueKeys.forEach((key) => {
395
- manifest.set(key, {
396
- key,
397
- upFile: resolveMigrationFile(directory, key, "up", dialect),
398
- downFile: resolveMigrationFile(directory, key, "down", dialect)
399
- });
400
- });
401
- return manifest;
402
- }
403
-
404
442
  // framework/MigrationDirectoryReader.ts
405
443
  var MigrationDirectoryReader = class {
406
444
  constructor(directory, read_strategy, sqlrunner, dialect = "sql") {
@@ -1288,7 +1326,7 @@ var PatchCreator = class _PatchCreator {
1288
1326
  if (!fs5.existsSync(this.patchFolder)) {
1289
1327
  fs5.mkdirSync(this.patchFolder, { recursive: true });
1290
1328
  }
1291
- let stamp = Date.now();
1329
+ let stamp = mintMigrationStamp();
1292
1330
  for (let attempt = 0; attempt < 1e3; attempt++) {
1293
1331
  const filePath = path4.join(this.patchFolder, `${stamp}_${normalized}.yaml`);
1294
1332
  try {
@@ -1296,7 +1334,7 @@ var PatchCreator = class _PatchCreator {
1296
1334
  return filePath;
1297
1335
  } catch (error) {
1298
1336
  if (error && error.code === "EEXIST") {
1299
- stamp += 1;
1337
+ stamp = mintMigrationStamp(stamp + 1);
1300
1338
  continue;
1301
1339
  }
1302
1340
  throw error;
@@ -1785,6 +1823,7 @@ var MySQLMigrationRunner = class {
1785
1823
  */
1786
1824
  this.preflightPromise = null;
1787
1825
  this.lastPatchResults = [];
1826
+ validateLegacyMigrationKeys(config.legacy_migration_keys);
1788
1827
  }
1789
1828
  setup() {
1790
1829
  return __async(this, null, function* () {
@@ -1890,7 +1929,17 @@ var MySQLMigrationRunner = class {
1890
1929
  }
1891
1930
  migrate(migrationNodes, forward) {
1892
1931
  return __async(this, null, function* () {
1932
+ var _a;
1893
1933
  yield this.setup();
1934
+ if (forward) {
1935
+ const legacyMigrationKeys = new Set((_a = this.config.legacy_migration_keys) != null ? _a : []);
1936
+ for (const node of migrationNodes) {
1937
+ const key = node.get_key();
1938
+ if (isHandStampedKey(key) && !legacyMigrationKeys.has(key) && !(yield node.status()).completed) {
1939
+ throw new MigrationStampError(key);
1940
+ }
1941
+ }
1942
+ }
1894
1943
  for (let node of migrationNodes) {
1895
1944
  try {
1896
1945
  if (forward) {
@@ -1986,6 +2035,7 @@ var FileMigrationConfigReader = class {
1986
2035
  try {
1987
2036
  const fileContent = fs6.readFileSync(this.configFile);
1988
2037
  const config = JSON.parse(fileContent.toString());
2038
+ validateLegacyMigrationKeys(config.legacy_migration_keys);
1989
2039
  if (!config.migration_folder) {
1990
2040
  throw ConfigurationError.missingRequiredProperty("migration_folder");
1991
2041
  }
@@ -2028,7 +2078,7 @@ var MigrationCreator = class {
2028
2078
  if (!fs6.existsSync(this.config.migration_folder)) {
2029
2079
  fs6.mkdirSync(this.config.migration_folder, { recursive: true });
2030
2080
  }
2031
- const now_timestamp = Date.now();
2081
+ const now_timestamp = mintMigrationStamp();
2032
2082
  const filename_up = `${now_timestamp}_${name}.up.sql`;
2033
2083
  const filename_down = `${now_timestamp}_${name}.down.sql`;
2034
2084
  fs6.writeFileSync(`${this.config.migration_folder}/${filename_up}`, `