@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.mjs CHANGED
@@ -121,7 +121,7 @@ var init_MigrationFilter = __esm({
121
121
  });
122
122
 
123
123
  // framework/errors.ts
124
- var MigrationError, ConfigurationError, DatabaseConnectionError, MigrationExecutionError, PatchError, PatchValidationError, PatchIntegrityError, PatchConflictError, PatchExecutionError, CLIError;
124
+ var MigrationError, ConfigurationError, DatabaseConnectionError, MigrationStampError, MigrationExecutionError, PatchError, PatchValidationError, PatchIntegrityError, PatchConflictError, PatchExecutionError, CLIError;
125
125
  var init_errors = __esm({
126
126
  "framework/errors.ts"() {
127
127
  MigrationError = class _MigrationError extends Error {
@@ -187,6 +187,16 @@ var init_errors = __esm({
187
187
  return new _DatabaseConnectionError(`Authentication failed for ${dbType} database`);
188
188
  }
189
189
  };
190
+ MigrationStampError = class _MigrationStampError extends MigrationError {
191
+ constructor(migrationKey) {
192
+ super(
193
+ `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.`
194
+ );
195
+ this.migrationKey = migrationKey;
196
+ this.name = "MigrationStampError";
197
+ Object.setPrototypeOf(this, _MigrationStampError.prototype);
198
+ }
199
+ };
190
200
  MigrationExecutionError = class _MigrationExecutionError extends MigrationError {
191
201
  constructor(message, migrationName, sql, originalError) {
192
202
  let fullMessage = `Migration Execution Error${migrationName ? ` in '${migrationName}'` : ""}: ${message}`;
@@ -317,6 +327,79 @@ ${originalError.message}` : message, patchFile, patchKey);
317
327
  }
318
328
  });
319
329
 
330
+ // framework/MigrationManifest.ts
331
+ import fs from "fs";
332
+ import path from "path";
333
+ function isMigrationFile(fileName) {
334
+ return MIGRATION_FILE_REGEX.test(fileName);
335
+ }
336
+ function canonicalMigrationKey(value) {
337
+ return value.replace(MIGRATION_FILE_REGEX, "").toLowerCase();
338
+ }
339
+ function mintMigrationStamp(now = Date.now()) {
340
+ return now % 10 === 0 ? now + 1 : now;
341
+ }
342
+ function isHandStampedKey(key) {
343
+ const stamp = key.split("_")[0];
344
+ return /^\d+$/.test(stamp) && /000$/.test(stamp);
345
+ }
346
+ function resolveMigrationFile(directory, baseName, direction, dialect) {
347
+ const dialectExt = dialect === "sql" ? "mysql" : dialect;
348
+ const dialectFile = path.join(directory, `${baseName}.${dialectExt}.${direction}.sql`);
349
+ if (fs.existsSync(dialectFile)) return dialectFile;
350
+ const genericFile = path.join(directory, `${baseName}.${direction}.sql`);
351
+ if (fs.existsSync(genericFile)) return genericFile;
352
+ return null;
353
+ }
354
+ function loadMigrationManifest(directory, dialect) {
355
+ const manifest = /* @__PURE__ */ new Map();
356
+ if (!fs.existsSync(directory)) return manifest;
357
+ const files = fs.readdirSync(directory, { withFileTypes: true }).filter((f) => f.isFile()).map((f) => f.name).filter(isMigrationFile);
358
+ const uniqueKeys = /* @__PURE__ */ new Set();
359
+ files.forEach((file) => uniqueKeys.add(canonicalMigrationKey(file)));
360
+ uniqueKeys.forEach((key) => {
361
+ manifest.set(key, {
362
+ key,
363
+ upFile: resolveMigrationFile(directory, key, "up", dialect),
364
+ downFile: resolveMigrationFile(directory, key, "down", dialect)
365
+ });
366
+ });
367
+ return manifest;
368
+ }
369
+ var MIGRATION_FILE_REGEX;
370
+ var init_MigrationManifest = __esm({
371
+ "framework/MigrationManifest.ts"() {
372
+ MIGRATION_FILE_REGEX = /(?:\.(mysql|sqlite|pg))?\.(up|down)\.(sql|js)$/i;
373
+ }
374
+ });
375
+
376
+ // framework/MigrationConfig.ts
377
+ function validateLegacyMigrationKeys(value) {
378
+ if (value === void 0) {
379
+ return;
380
+ }
381
+ if (!Array.isArray(value) || value.some((key) => !isExactCanonicalMigrationKey(key))) {
382
+ throw new ConfigurationError(
383
+ "legacy_migration_keys must be an array of nonempty exact canonical migration keys; filenames, paths, wildcards, and nonstrings are not allowed"
384
+ );
385
+ }
386
+ }
387
+ function isExactCanonicalMigrationKey(value) {
388
+ if (typeof value !== "string" || value.length === 0 || value !== value.trim()) {
389
+ return false;
390
+ }
391
+ if (value.includes("/") || value.includes("\\") || ["*", "?", "[", "]", "{", "}"].some((token) => value.includes(token))) {
392
+ return false;
393
+ }
394
+ return !isMigrationFile(value) && canonicalMigrationKey(value) === value;
395
+ }
396
+ var init_MigrationConfig = __esm({
397
+ "framework/MigrationConfig.ts"() {
398
+ init_errors();
399
+ init_MigrationManifest();
400
+ }
401
+ });
402
+
320
403
  // framework/MigrationNode.ts
321
404
  var MigrationNode, SqlMigrationNode;
322
405
  var init_MigrationNode = __esm({
@@ -460,45 +543,6 @@ var init_SqlMigrationBuilder = __esm({
460
543
  }
461
544
  });
462
545
 
463
- // framework/MigrationManifest.ts
464
- import fs from "fs";
465
- import path from "path";
466
- function isMigrationFile(fileName) {
467
- return MIGRATION_FILE_REGEX.test(fileName);
468
- }
469
- function canonicalMigrationKey(value) {
470
- return value.replace(MIGRATION_FILE_REGEX, "").toLowerCase();
471
- }
472
- function resolveMigrationFile(directory, baseName, direction, dialect) {
473
- const dialectExt = dialect === "sql" ? "mysql" : dialect;
474
- const dialectFile = path.join(directory, `${baseName}.${dialectExt}.${direction}.sql`);
475
- if (fs.existsSync(dialectFile)) return dialectFile;
476
- const genericFile = path.join(directory, `${baseName}.${direction}.sql`);
477
- if (fs.existsSync(genericFile)) return genericFile;
478
- return null;
479
- }
480
- function loadMigrationManifest(directory, dialect) {
481
- const manifest = /* @__PURE__ */ new Map();
482
- if (!fs.existsSync(directory)) return manifest;
483
- const files = fs.readdirSync(directory, { withFileTypes: true }).filter((f) => f.isFile()).map((f) => f.name).filter(isMigrationFile);
484
- const uniqueKeys = /* @__PURE__ */ new Set();
485
- files.forEach((file) => uniqueKeys.add(canonicalMigrationKey(file)));
486
- uniqueKeys.forEach((key) => {
487
- manifest.set(key, {
488
- key,
489
- upFile: resolveMigrationFile(directory, key, "up", dialect),
490
- downFile: resolveMigrationFile(directory, key, "down", dialect)
491
- });
492
- });
493
- return manifest;
494
- }
495
- var MIGRATION_FILE_REGEX;
496
- var init_MigrationManifest = __esm({
497
- "framework/MigrationManifest.ts"() {
498
- MIGRATION_FILE_REGEX = /(?:\.(mysql|sqlite|pg))?\.(up|down)\.(sql|js)$/i;
499
- }
500
- });
501
-
502
546
  // framework/MigrationDirectoryReader.ts
503
547
  import fs2 from "fs";
504
548
  var MigrationDirectoryReader;
@@ -1375,6 +1419,7 @@ var MAX_NAME_LENGTH, SCAFFOLD, PatchCreator;
1375
1419
  var init_PatchCreator = __esm({
1376
1420
  "framework/PatchCreator.ts"() {
1377
1421
  init_errors();
1422
+ init_MigrationManifest();
1378
1423
  MAX_NAME_LENGTH = 120;
1379
1424
  SCAFFOLD = `version: 1
1380
1425
  description: TODO
@@ -1421,7 +1466,7 @@ operations: []
1421
1466
  if (!fs5.existsSync(this.patchFolder)) {
1422
1467
  fs5.mkdirSync(this.patchFolder, { recursive: true });
1423
1468
  }
1424
- let stamp = Date.now();
1469
+ let stamp = mintMigrationStamp();
1425
1470
  for (let attempt = 0; attempt < 1e3; attempt++) {
1426
1471
  const filePath = path4.join(this.patchFolder, `${stamp}_${normalized}.yaml`);
1427
1472
  try {
@@ -1429,7 +1474,7 @@ operations: []
1429
1474
  return filePath;
1430
1475
  } catch (error) {
1431
1476
  if (error && error.code === "EEXIST") {
1432
- stamp += 1;
1477
+ stamp = mintMigrationStamp(stamp + 1);
1433
1478
  continue;
1434
1479
  }
1435
1480
  throw error;
@@ -1780,6 +1825,7 @@ function makeRunner(database, conn) {
1780
1825
  var MigrationRunnerFactory, MySQLMigrationRunner, FileMigrationConfigReader, MigrationCreator;
1781
1826
  var init_MigrationRunner = __esm({
1782
1827
  "framework/MigrationRunner.ts"() {
1828
+ init_MigrationConfig();
1783
1829
  init_MigrationDirectoryReader();
1784
1830
  init_MigrationSetup();
1785
1831
  init_MigrationFilter();
@@ -1787,6 +1833,8 @@ var init_MigrationRunner = __esm({
1787
1833
  init_PatchRunner();
1788
1834
  init_PatchCreator();
1789
1835
  init_PatchTypes();
1836
+ init_MigrationManifest();
1837
+ init_errors();
1790
1838
  init_SQLRunner();
1791
1839
  init_errors();
1792
1840
  MigrationRunnerFactory = class _MigrationRunnerFactory {
@@ -1934,6 +1982,7 @@ var init_MigrationRunner = __esm({
1934
1982
  */
1935
1983
  this.preflightPromise = null;
1936
1984
  this.lastPatchResults = [];
1985
+ validateLegacyMigrationKeys(config.legacy_migration_keys);
1937
1986
  }
1938
1987
  setup() {
1939
1988
  return __async(this, null, function* () {
@@ -2039,7 +2088,17 @@ var init_MigrationRunner = __esm({
2039
2088
  }
2040
2089
  migrate(migrationNodes, forward) {
2041
2090
  return __async(this, null, function* () {
2091
+ var _a;
2042
2092
  yield this.setup();
2093
+ if (forward) {
2094
+ const legacyMigrationKeys = new Set((_a = this.config.legacy_migration_keys) != null ? _a : []);
2095
+ for (const node of migrationNodes) {
2096
+ const key = node.get_key();
2097
+ if (isHandStampedKey(key) && !legacyMigrationKeys.has(key) && !(yield node.status()).completed) {
2098
+ throw new MigrationStampError(key);
2099
+ }
2100
+ }
2101
+ }
2043
2102
  for (let node of migrationNodes) {
2044
2103
  try {
2045
2104
  if (forward) {
@@ -2135,6 +2194,7 @@ var init_MigrationRunner = __esm({
2135
2194
  try {
2136
2195
  const fileContent = fs6.readFileSync(this.configFile);
2137
2196
  const config = JSON.parse(fileContent.toString());
2197
+ validateLegacyMigrationKeys(config.legacy_migration_keys);
2138
2198
  if (!config.migration_folder) {
2139
2199
  throw ConfigurationError.missingRequiredProperty("migration_folder");
2140
2200
  }
@@ -2177,7 +2237,7 @@ var init_MigrationRunner = __esm({
2177
2237
  if (!fs6.existsSync(this.config.migration_folder)) {
2178
2238
  fs6.mkdirSync(this.config.migration_folder, { recursive: true });
2179
2239
  }
2180
- const now_timestamp = Date.now();
2240
+ const now_timestamp = mintMigrationStamp();
2181
2241
  const filename_up = `${now_timestamp}_${name}.up.sql`;
2182
2242
  const filename_down = `${now_timestamp}_${name}.down.sql`;
2183
2243
  fs6.writeFileSync(`${this.config.migration_folder}/${filename_up}`, `