@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/cli.js CHANGED
@@ -125,9 +125,6 @@ function migration_filter(_0) {
125
125
  // framework/MigrationRunner.ts
126
126
  var import_fs6 = __toESM(require("fs"));
127
127
 
128
- // framework/MigrationDirectoryReader.ts
129
- var import_fs2 = __toESM(require("fs"));
130
-
131
128
  // framework/errors.ts
132
129
  var MigrationError = class _MigrationError extends Error {
133
130
  constructor(message) {
@@ -192,6 +189,16 @@ var DatabaseConnectionError = class _DatabaseConnectionError extends MigrationEr
192
189
  return new _DatabaseConnectionError(`Authentication failed for ${dbType} database`);
193
190
  }
194
191
  };
192
+ var MigrationStampError = class _MigrationStampError extends MigrationError {
193
+ constructor(migrationKey) {
194
+ super(
195
+ `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.`
196
+ );
197
+ this.migrationKey = migrationKey;
198
+ this.name = "MigrationStampError";
199
+ Object.setPrototypeOf(this, _MigrationStampError.prototype);
200
+ }
201
+ };
195
202
  var MigrationExecutionError = class _MigrationExecutionError extends MigrationError {
196
203
  constructor(message, migrationName, sql, originalError) {
197
204
  let fullMessage = `Migration Execution Error${migrationName ? ` in '${migrationName}'` : ""}: ${message}`;
@@ -320,6 +327,71 @@ var CLIError = class _CLIError extends MigrationError {
320
327
  }
321
328
  };
322
329
 
330
+ // framework/MigrationManifest.ts
331
+ var import_fs = __toESM(require("fs"));
332
+ var import_path = __toESM(require("path"));
333
+ var MIGRATION_FILE_REGEX = /(?:\.(mysql|sqlite|pg))?\.(up|down)\.(sql|js)$/i;
334
+ function isMigrationFile(fileName) {
335
+ return MIGRATION_FILE_REGEX.test(fileName);
336
+ }
337
+ function canonicalMigrationKey(value) {
338
+ return value.replace(MIGRATION_FILE_REGEX, "").toLowerCase();
339
+ }
340
+ function mintMigrationStamp(now = Date.now()) {
341
+ return now % 10 === 0 ? now + 1 : now;
342
+ }
343
+ function isHandStampedKey(key) {
344
+ const stamp = key.split("_")[0];
345
+ return /^\d+$/.test(stamp) && /000$/.test(stamp);
346
+ }
347
+ function resolveMigrationFile(directory, baseName, direction, dialect) {
348
+ const dialectExt = dialect === "sql" ? "mysql" : dialect;
349
+ const dialectFile = import_path.default.join(directory, `${baseName}.${dialectExt}.${direction}.sql`);
350
+ if (import_fs.default.existsSync(dialectFile)) return dialectFile;
351
+ const genericFile = import_path.default.join(directory, `${baseName}.${direction}.sql`);
352
+ if (import_fs.default.existsSync(genericFile)) return genericFile;
353
+ return null;
354
+ }
355
+ function loadMigrationManifest(directory, dialect) {
356
+ const manifest = /* @__PURE__ */ new Map();
357
+ if (!import_fs.default.existsSync(directory)) return manifest;
358
+ const files = import_fs.default.readdirSync(directory, { withFileTypes: true }).filter((f) => f.isFile()).map((f) => f.name).filter(isMigrationFile);
359
+ const uniqueKeys = /* @__PURE__ */ new Set();
360
+ files.forEach((file) => uniqueKeys.add(canonicalMigrationKey(file)));
361
+ uniqueKeys.forEach((key) => {
362
+ manifest.set(key, {
363
+ key,
364
+ upFile: resolveMigrationFile(directory, key, "up", dialect),
365
+ downFile: resolveMigrationFile(directory, key, "down", dialect)
366
+ });
367
+ });
368
+ return manifest;
369
+ }
370
+
371
+ // framework/MigrationConfig.ts
372
+ function validateLegacyMigrationKeys(value) {
373
+ if (value === void 0) {
374
+ return;
375
+ }
376
+ if (!Array.isArray(value) || value.some((key) => !isExactCanonicalMigrationKey(key))) {
377
+ throw new ConfigurationError(
378
+ "legacy_migration_keys must be an array of nonempty exact canonical migration keys; filenames, paths, wildcards, and nonstrings are not allowed"
379
+ );
380
+ }
381
+ }
382
+ function isExactCanonicalMigrationKey(value) {
383
+ if (typeof value !== "string" || value.length === 0 || value !== value.trim()) {
384
+ return false;
385
+ }
386
+ if (value.includes("/") || value.includes("\\") || ["*", "?", "[", "]", "{", "}"].some((token) => value.includes(token))) {
387
+ return false;
388
+ }
389
+ return !isMigrationFile(value) && canonicalMigrationKey(value) === value;
390
+ }
391
+
392
+ // framework/MigrationDirectoryReader.ts
393
+ var import_fs2 = __toESM(require("fs"));
394
+
323
395
  // framework/MigrationNode.ts
324
396
  var MigrationNode = class {
325
397
  constructor(name) {
@@ -451,40 +523,6 @@ var SqlMigrationBuilder = class {
451
523
  }
452
524
  };
453
525
 
454
- // framework/MigrationManifest.ts
455
- var import_fs = __toESM(require("fs"));
456
- var import_path = __toESM(require("path"));
457
- var MIGRATION_FILE_REGEX = /(?:\.(mysql|sqlite|pg))?\.(up|down)\.(sql|js)$/i;
458
- function isMigrationFile(fileName) {
459
- return MIGRATION_FILE_REGEX.test(fileName);
460
- }
461
- function canonicalMigrationKey(value) {
462
- return value.replace(MIGRATION_FILE_REGEX, "").toLowerCase();
463
- }
464
- function resolveMigrationFile(directory, baseName, direction, dialect) {
465
- const dialectExt = dialect === "sql" ? "mysql" : dialect;
466
- const dialectFile = import_path.default.join(directory, `${baseName}.${dialectExt}.${direction}.sql`);
467
- if (import_fs.default.existsSync(dialectFile)) return dialectFile;
468
- const genericFile = import_path.default.join(directory, `${baseName}.${direction}.sql`);
469
- if (import_fs.default.existsSync(genericFile)) return genericFile;
470
- return null;
471
- }
472
- function loadMigrationManifest(directory, dialect) {
473
- const manifest = /* @__PURE__ */ new Map();
474
- if (!import_fs.default.existsSync(directory)) return manifest;
475
- const files = import_fs.default.readdirSync(directory, { withFileTypes: true }).filter((f) => f.isFile()).map((f) => f.name).filter(isMigrationFile);
476
- const uniqueKeys = /* @__PURE__ */ new Set();
477
- files.forEach((file) => uniqueKeys.add(canonicalMigrationKey(file)));
478
- uniqueKeys.forEach((key) => {
479
- manifest.set(key, {
480
- key,
481
- upFile: resolveMigrationFile(directory, key, "up", dialect),
482
- downFile: resolveMigrationFile(directory, key, "down", dialect)
483
- });
484
- });
485
- return manifest;
486
- }
487
-
488
526
  // framework/MigrationDirectoryReader.ts
489
527
  var MigrationDirectoryReader = class {
490
528
  constructor(directory, read_strategy, sqlrunner, dialect = "sql") {
@@ -1357,7 +1395,7 @@ var PatchCreator = class _PatchCreator {
1357
1395
  if (!import_fs5.default.existsSync(this.patchFolder)) {
1358
1396
  import_fs5.default.mkdirSync(this.patchFolder, { recursive: true });
1359
1397
  }
1360
- let stamp = Date.now();
1398
+ let stamp = mintMigrationStamp();
1361
1399
  for (let attempt = 0; attempt < 1e3; attempt++) {
1362
1400
  const filePath = import_path4.default.join(this.patchFolder, `${stamp}_${normalized}.yaml`);
1363
1401
  try {
@@ -1365,7 +1403,7 @@ var PatchCreator = class _PatchCreator {
1365
1403
  return filePath;
1366
1404
  } catch (error) {
1367
1405
  if (error && error.code === "EEXIST") {
1368
- stamp += 1;
1406
+ stamp = mintMigrationStamp(stamp + 1);
1369
1407
  continue;
1370
1408
  }
1371
1409
  throw error;
@@ -1850,6 +1888,7 @@ var MySQLMigrationRunner = class {
1850
1888
  */
1851
1889
  this.preflightPromise = null;
1852
1890
  this.lastPatchResults = [];
1891
+ validateLegacyMigrationKeys(config.legacy_migration_keys);
1853
1892
  }
1854
1893
  setup() {
1855
1894
  return __async(this, null, function* () {
@@ -1955,7 +1994,17 @@ var MySQLMigrationRunner = class {
1955
1994
  }
1956
1995
  migrate(migrationNodes, forward) {
1957
1996
  return __async(this, null, function* () {
1997
+ var _a;
1958
1998
  yield this.setup();
1999
+ if (forward) {
2000
+ const legacyMigrationKeys = new Set((_a = this.config.legacy_migration_keys) != null ? _a : []);
2001
+ for (const node of migrationNodes) {
2002
+ const key = node.get_key();
2003
+ if (isHandStampedKey(key) && !legacyMigrationKeys.has(key) && !(yield node.status()).completed) {
2004
+ throw new MigrationStampError(key);
2005
+ }
2006
+ }
2007
+ }
1959
2008
  for (let node of migrationNodes) {
1960
2009
  try {
1961
2010
  if (forward) {
@@ -2051,6 +2100,7 @@ var FileMigrationConfigReader = class {
2051
2100
  try {
2052
2101
  const fileContent = import_fs6.default.readFileSync(this.configFile);
2053
2102
  const config = JSON.parse(fileContent.toString());
2103
+ validateLegacyMigrationKeys(config.legacy_migration_keys);
2054
2104
  if (!config.migration_folder) {
2055
2105
  throw ConfigurationError.missingRequiredProperty("migration_folder");
2056
2106
  }
@@ -2093,7 +2143,7 @@ var MigrationCreator = class {
2093
2143
  if (!import_fs6.default.existsSync(this.config.migration_folder)) {
2094
2144
  import_fs6.default.mkdirSync(this.config.migration_folder, { recursive: true });
2095
2145
  }
2096
- const now_timestamp = Date.now();
2146
+ const now_timestamp = mintMigrationStamp();
2097
2147
  const filename_up = `${now_timestamp}_${name}.up.sql`;
2098
2148
  const filename_down = `${now_timestamp}_${name}.down.sql`;
2099
2149
  import_fs6.default.writeFileSync(`${this.config.migration_folder}/${filename_up}`, `