pgflow 0.2.1 → 0.2.3

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.
@@ -1,4 +1,4 @@
1
- export declare function copyMigrations({ supabasePath, autoConfirm }: {
1
+ export declare function copyMigrations({ supabasePath, autoConfirm, }: {
2
2
  supabasePath: string;
3
3
  autoConfirm?: boolean;
4
4
  }): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"copy-migrations.d.ts","sourceRoot":"","sources":["../../../src/commands/install/copy-migrations.ts"],"names":[],"mappings":"AAyEA,wBAAsB,cAAc,CAAC,EACnC,YAAY,EACZ,WAAmB,EACpB,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,OAAO,CAAC,CAoGnB"}
1
+ {"version":3,"file":"copy-migrations.d.ts","sourceRoot":"","sources":["../../../src/commands/install/copy-migrations.ts"],"names":[],"mappings":"AA6JA,wBAAsB,cAAc,CAAC,EACnC,YAAY,EACZ,WAAmB,GACpB,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,OAAO,CAAC,CA6LnB"}
@@ -44,10 +44,77 @@ function findMigrationsDirectory() {
44
44
  // No migrations found
45
45
  return null;
46
46
  }
47
+ // Helper function to get the timestamp part from a migration filename
48
+ function getTimestampFromFilename(filename) {
49
+ const match = filename.match(/^(\d+)_/);
50
+ // Return the timestamp only if it exists and has the correct length (14 digits)
51
+ if (match && match[1] && match[1].length === 14 && /^\d{14}$/.test(match[1])) {
52
+ return match[1];
53
+ }
54
+ return '';
55
+ }
56
+ // Helper function to format a Date object into a migration timestamp string (YYYYMMDDhhmmss)
57
+ function formatDateToTimestamp(date) {
58
+ const year = date.getFullYear();
59
+ const month = String(date.getMonth() + 1).padStart(2, '0');
60
+ const day = String(date.getDate()).padStart(2, '0');
61
+ const hours = String(date.getHours()).padStart(2, '0');
62
+ const minutes = String(date.getMinutes()).padStart(2, '0');
63
+ const seconds = String(date.getSeconds()).padStart(2, '0');
64
+ return `${year}${month}${day}${hours}${minutes}${seconds}`;
65
+ }
66
+ // Helper function to parse a timestamp string into a Date object
67
+ function parseTimestampToDate(timestamp) {
68
+ // Validate format: YYYYMMDDhhmmss
69
+ if (!timestamp || timestamp.length !== 14 || !/^\d{14}$/.test(timestamp)) {
70
+ return null;
71
+ }
72
+ const year = parseInt(timestamp.substring(0, 4), 10);
73
+ const month = parseInt(timestamp.substring(4, 6), 10) - 1; // months are 0-indexed in JS Date
74
+ const day = parseInt(timestamp.substring(6, 8), 10);
75
+ const hours = parseInt(timestamp.substring(8, 10), 10);
76
+ const minutes = parseInt(timestamp.substring(10, 12), 10);
77
+ const seconds = parseInt(timestamp.substring(12, 14), 10);
78
+ // Create date and validate (invalid dates like Feb 31 will be auto-corrected by JS Date)
79
+ const date = new Date(year, month, day, hours, minutes, seconds);
80
+ // Additional validation to ensure the parsed date matches the input
81
+ // This catches edge cases like month=13 that JS Date would autocorrect
82
+ if (date.getFullYear() !== year ||
83
+ date.getMonth() !== month ||
84
+ date.getDate() !== day ||
85
+ date.getHours() !== hours ||
86
+ date.getMinutes() !== minutes ||
87
+ date.getSeconds() !== seconds) {
88
+ return null;
89
+ }
90
+ return date;
91
+ }
92
+ // Helper function to generate a new timestamp that's higher than the reference timestamp
93
+ function generateNewTimestamp(referenceTimestamp, increment = 1) {
94
+ // First try to parse the reference timestamp to a Date
95
+ const parsedDate = parseTimestampToDate(referenceTimestamp);
96
+ // If we couldn't parse it, use current time
97
+ if (!parsedDate) {
98
+ return formatDateToTimestamp(new Date());
99
+ }
100
+ // Add the specified number of seconds (default: 1)
101
+ parsedDate.setSeconds(parsedDate.getSeconds() + increment);
102
+ // Get current time for comparison
103
+ const now = new Date();
104
+ // Return either the incremented timestamp or current time, whichever is later
105
+ // This ensures we never go backwards in time
106
+ if (parsedDate > now) {
107
+ return formatDateToTimestamp(parsedDate);
108
+ }
109
+ else {
110
+ // If we're already at or past current time, add increment to now
111
+ now.setSeconds(now.getSeconds() + increment);
112
+ return formatDateToTimestamp(now);
113
+ }
114
+ }
47
115
  // Find the migrations directory
48
116
  const sourcePath = findMigrationsDirectory();
49
- export async function copyMigrations({ supabasePath, autoConfirm = false }) {
50
- // Check migrations
117
+ export async function copyMigrations({ supabasePath, autoConfirm = false, }) {
51
118
  const migrationsPath = path.join(supabasePath, 'migrations');
52
119
  if (!fs.existsSync(migrationsPath)) {
53
120
  fs.mkdirSync(migrationsPath);
@@ -60,38 +127,99 @@ export async function copyMigrations({ supabasePath, autoConfirm = false }) {
60
127
  log.info('If running in development mode, try building the core package first with: nx build core');
61
128
  return false;
62
129
  }
63
- const files = fs.readdirSync(sourcePath);
130
+ // Get all existing migrations in user's directory
131
+ const existingFiles = fs.existsSync(migrationsPath)
132
+ ? fs.readdirSync(migrationsPath)
133
+ : [];
134
+ // Find the latest migration timestamp in user's directory
135
+ let latestTimestamp = '00000000000000';
136
+ for (const file of existingFiles) {
137
+ if (file.endsWith('.sql')) {
138
+ const timestamp = getTimestampFromFilename(file);
139
+ // Only consider timestamps that have been validated by getTimestampFromFilename
140
+ // to have the correct length and format
141
+ if (timestamp && timestamp.length === 14) {
142
+ const parsedDate = parseTimestampToDate(timestamp);
143
+ // If we have a valid date and this timestamp is newer, update latestTimestamp
144
+ if (parsedDate && parseInt(timestamp, 10) > parseInt(latestTimestamp, 10)) {
145
+ latestTimestamp = timestamp;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ // Get all source migrations
151
+ const sourceFiles = fs
152
+ .readdirSync(sourcePath)
153
+ .filter((file) => file.endsWith('.sql'));
64
154
  const filesToCopy = [];
65
155
  const skippedFiles = [];
66
- // Determine which SQL files need to be copied
67
- for (const file of files) {
68
- // Only process SQL files
69
- if (!file.endsWith('.sql')) {
70
- continue;
71
- }
72
- const destination = path.join(migrationsPath, file);
73
- if (fs.existsSync(destination)) {
74
- skippedFiles.push(file);
156
+ // Check which migrations need to be installed
157
+ for (const sourceFile of sourceFiles) {
158
+ // Check if this migration is already installed (by checking if the original filename
159
+ // appears in any existing migration filename)
160
+ const isAlreadyInstalled = existingFiles.some((existingFile) => existingFile.includes(sourceFile));
161
+ if (isAlreadyInstalled) {
162
+ skippedFiles.push(sourceFile);
75
163
  }
76
164
  else {
77
- filesToCopy.push(file);
165
+ filesToCopy.push({
166
+ source: sourceFile,
167
+ destination: sourceFile, // Will be updated later with new timestamp
168
+ });
78
169
  }
79
170
  }
80
- // If no files to copy, show message and return false (no changes made)
171
+ // If no files to copy, show message with details and return false (no changes made)
81
172
  if (filesToCopy.length === 0) {
173
+ // Show success message
82
174
  log.success('All pgflow migrations are already in place');
175
+ // Show details of already installed migrations
176
+ if (skippedFiles.length > 0) {
177
+ const detailedMsg = [
178
+ 'Already installed migrations:',
179
+ ...skippedFiles.map((file) => {
180
+ // Find the matching existing file to show how it was installed
181
+ const matchingFile = existingFiles.find((existingFile) => existingFile.includes(file));
182
+ if (matchingFile === file) {
183
+ // Installed with old direct method
184
+ return ` ${chalk.dim('•')} ${chalk.bold(file)}`;
185
+ }
186
+ else {
187
+ // Installed with new timestamped method
188
+ const timestampPart = matchingFile?.substring(0, matchingFile.indexOf(file) - 1) || '';
189
+ return ` ${chalk.dim('•')} ${chalk.dim(timestampPart + '_')}${chalk.bold(file)}`;
190
+ }
191
+ }),
192
+ ].join('\n');
193
+ note(detailedMsg, 'Existing pgflow Migrations');
194
+ }
83
195
  return false;
84
196
  }
197
+ // Generate new timestamps for migrations to install
198
+ let baseTimestamp = latestTimestamp;
199
+ filesToCopy.forEach((file) => {
200
+ // Generate timestamp with increasing values to maintain order
201
+ // Each iteration uses the timestamp generated by the previous iteration as the base
202
+ baseTimestamp = generateNewTimestamp(baseTimestamp);
203
+ // Create new filename with format: newTimestamp_originalFilename
204
+ file.destination = `${baseTimestamp}_${file.source}`;
205
+ });
85
206
  log.info(`Found ${filesToCopy.length} migration${filesToCopy.length !== 1 ? 's' : ''} to install`);
86
207
  // Prepare summary message with colored output
87
208
  const summaryParts = [];
88
209
  if (filesToCopy.length > 0) {
89
- summaryParts.push(`${chalk.green('New migrations to install:')}
90
- ${filesToCopy.map((file) => `${chalk.green('+')} ${file}`).join('\n')}`);
210
+ summaryParts.push(`${chalk.green('New migrations to install:')}\n${filesToCopy
211
+ .map((file) => {
212
+ // Extract the timestamp part from the new filename
213
+ const newTimestamp = file.destination.substring(0, 14);
214
+ // Format: dim timestamp + bright original name
215
+ return `${chalk.green('+')} ${file.source} → ${chalk.dim(newTimestamp + '_')}${chalk.bold(file.source)}`;
216
+ })
217
+ .join('\n')}`);
91
218
  }
92
219
  if (skippedFiles.length > 0) {
93
- summaryParts.push(`${chalk.yellow('Already installed:')}
94
- ${skippedFiles.map((file) => `${chalk.yellow('•')} ${file}`).join('\n')}`);
220
+ summaryParts.push(`${chalk.yellow('Already installed:')}\n${skippedFiles
221
+ .map((file) => `${chalk.yellow('•')} ${file}`)
222
+ .join('\n')}`);
95
223
  }
96
224
  // Show summary and ask for confirmation if not auto-confirming
97
225
  note(summaryParts.join('\n\n'), 'pgflow Migrations');
@@ -106,13 +234,20 @@ ${skippedFiles.map((file) => `${chalk.yellow('•')} ${file}`).join('\n')}`);
106
234
  log.warn('Migration installation skipped');
107
235
  return false;
108
236
  }
109
- // Install migrations
110
- // Copy the files
237
+ // Install migrations with new filenames
111
238
  for (const file of filesToCopy) {
112
- const source = path.join(sourcePath, file);
113
- const destination = path.join(migrationsPath, file);
114
- fs.copyFileSync(source, destination);
239
+ const sourcePath1 = path.join(sourcePath, file.source);
240
+ const destinationPath = path.join(migrationsPath, file.destination);
241
+ fs.copyFileSync(sourcePath1, destinationPath);
115
242
  }
116
- log.success(`Installed ${filesToCopy.length} migration${filesToCopy.length !== 1 ? 's' : ''} to your Supabase project`);
243
+ // Show detailed success message with styled filenames
244
+ const detailedSuccessMsg = [
245
+ `Installed ${filesToCopy.length} migration${filesToCopy.length !== 1 ? 's' : ''} to your Supabase project:`,
246
+ ...filesToCopy.map((file) => {
247
+ const newTimestamp = file.destination.substring(0, 14);
248
+ return ` ${chalk.dim(newTimestamp + '_')}${chalk.bold(file.source)}`;
249
+ }),
250
+ ].join('\n');
251
+ log.success(detailedSuccessMsg);
117
252
  return true; // Return true to indicate migrations were copied
118
253
  }
@@ -83,7 +83,7 @@ export default (program) => {
83
83
  // If we have specific steps, add another newline
84
84
  outroMessages.push('');
85
85
  }
86
- outroMessages.push(chalk.bold('Continue the setup:'), chalk.blue.underline('https://pgflow.dev/getting-started/compile-to-sql/'));
86
+ outroMessages.push(chalk.bold('Continue the setup:'), chalk.blue.underline('https://pgflow.dev/getting-started/create-first-flow/'));
87
87
  // Single outro for all paths
88
88
  outro(outroMessages.join('\n'));
89
89
  });
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgflow",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "typings": "./dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgflow",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "typings": "./dist/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "chalk": "^5.4.1",
25
25
  "commander": "^13.1.0",
26
26
  "toml-patch": "^0.2.3",
27
- "@pgflow/core": "0.2.1"
27
+ "@pgflow/core": "0.2.3"
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public"