pgflow 0.2.1 → 0.2.2
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-migrations.d.ts","sourceRoot":"","sources":["../../../src/commands/install/copy-migrations.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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
|
-
//
|
|
67
|
-
for (const
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
113
|
-
const
|
|
114
|
-
fs.copyFileSync(
|
|
239
|
+
const sourcePath1 = path.join(sourcePath, file.source);
|
|
240
|
+
const destinationPath = path.join(migrationsPath, file.destination);
|
|
241
|
+
fs.copyFileSync(sourcePath1, destinationPath);
|
|
115
242
|
}
|
|
116
|
-
|
|
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
|
}
|
package/dist/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgflow",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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.
|
|
27
|
+
"@pgflow/core": "0.2.2"
|
|
28
28
|
},
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|