pg-seed-kit 0.0.1
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/LICENSE +21 -0
- package/README.md +132 -0
- package/bin/pg-seed-kit.js +2 -0
- package/dist/adapters/drizzle.cjs +53 -0
- package/dist/adapters/drizzle.d.cts +25 -0
- package/dist/adapters/drizzle.d.ts +25 -0
- package/dist/adapters/drizzle.js +28 -0
- package/dist/adapters/prisma.cjs +45 -0
- package/dist/adapters/prisma.d.cts +22 -0
- package/dist/adapters/prisma.d.ts +22 -0
- package/dist/adapters/prisma.js +20 -0
- package/dist/adapters/sequelize.cjs +41 -0
- package/dist/adapters/sequelize.d.cts +21 -0
- package/dist/adapters/sequelize.d.ts +21 -0
- package/dist/adapters/sequelize.js +16 -0
- package/dist/adapters/typeorm.cjs +40 -0
- package/dist/adapters/typeorm.d.cts +18 -0
- package/dist/adapters/typeorm.d.ts +18 -0
- package/dist/adapters/typeorm.js +15 -0
- package/dist/chunk-KMXBCJZY.js +247 -0
- package/dist/cli/index.cjs +385 -0
- package/dist/cli/index.d.cts +2 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +143 -0
- package/dist/index.cjs +286 -0
- package/dist/index.d.cts +43 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +16 -0
- package/dist/types-CCBHgrLA.d.cts +57 -0
- package/dist/types-CCBHgrLA.d.ts +57 -0
- package/package.json +127 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { pathToFileURL } from "url";
|
|
5
|
+
|
|
6
|
+
// src/dynamic-import.ts
|
|
7
|
+
var dynamicImport = new Function("specifier", "return import(specifier);");
|
|
8
|
+
|
|
9
|
+
// src/config.ts
|
|
10
|
+
var DEFAULT_TABLE_NAME = "seeders";
|
|
11
|
+
var DEFAULT_FILE_PATTERN = /^\d{14}-.+\.seeder\.(ts|js|mjs|cjs)$/;
|
|
12
|
+
var CONFIG_FILES = ["pg-seed-kit.config.js", "pg-seed-kit.config.cjs", "pg-seed-kit.config.mjs"];
|
|
13
|
+
async function loadConfigFromFile(cwd) {
|
|
14
|
+
for (const filename of CONFIG_FILES) {
|
|
15
|
+
const filepath = path.resolve(cwd, filename);
|
|
16
|
+
if (fs.existsSync(filepath)) {
|
|
17
|
+
const loaded = await dynamicImport(pathToFileURL(filepath).href);
|
|
18
|
+
return loaded.default ?? loaded;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const pkgPath = path.resolve(cwd, "package.json");
|
|
22
|
+
if (fs.existsSync(pkgPath)) {
|
|
23
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
24
|
+
if (pkg["pg-seed-kit"]) {
|
|
25
|
+
return pkg["pg-seed-kit"];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function isResolver(value) {
|
|
31
|
+
return typeof value === "function";
|
|
32
|
+
}
|
|
33
|
+
async function loadConfig(overrides) {
|
|
34
|
+
const cwd = process.cwd();
|
|
35
|
+
const fileConfig = await loadConfigFromFile(cwd);
|
|
36
|
+
const merged = { ...fileConfig, ...overrides };
|
|
37
|
+
if (!merged.seedersPath) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'pg-seed-kit: "seedersPath" is required. Provide it via pg-seed-kit.config.js, package.json, or inline config.'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const rawPath = isResolver(merged.seedersPath) ? merged.seedersPath() : merged.seedersPath;
|
|
43
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) {
|
|
44
|
+
throw new Error('pg-seed-kit: "seedersPath" must resolve to a non-empty string.');
|
|
45
|
+
}
|
|
46
|
+
const seedersPath = path.isAbsolute(rawPath) ? rawPath : path.resolve(cwd, rawPath);
|
|
47
|
+
return {
|
|
48
|
+
seedersPath,
|
|
49
|
+
tableName: merged.tableName ?? DEFAULT_TABLE_NAME,
|
|
50
|
+
filePattern: merged.filePattern ?? DEFAULT_FILE_PATTERN,
|
|
51
|
+
adapter: merged.adapter,
|
|
52
|
+
connect: merged.connect
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function resolveSeedersPath(opts) {
|
|
56
|
+
return () => {
|
|
57
|
+
if (opts.srcWhen && opts.srcWhen()) return opts.src;
|
|
58
|
+
const argvFile = process.argv[1] || "";
|
|
59
|
+
if (/[\\/]dist[\\/]/.test(argvFile)) return opts.dist;
|
|
60
|
+
const isTsRuntime = /\.(ts|tsx)$/.test(argvFile) || Boolean(
|
|
61
|
+
process._preload_modules?.some(
|
|
62
|
+
(m) => /tsx|ts-node/.test(m)
|
|
63
|
+
)
|
|
64
|
+
) || Boolean(process.env.TS_NODE_DEV) || Boolean(process.env.TSX);
|
|
65
|
+
return isTsRuntime ? opts.src : opts.dist;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/runner.ts
|
|
70
|
+
import fs2 from "fs";
|
|
71
|
+
import path2 from "path";
|
|
72
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
73
|
+
|
|
74
|
+
// src/tracker.ts
|
|
75
|
+
var ensuredTables = /* @__PURE__ */ new Set();
|
|
76
|
+
function quoteIdentifier(name) {
|
|
77
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`pg-seed-kit: invalid table name "${name}". Use only letters, digits, and underscores (must not start with a digit).`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return `"${name}"`;
|
|
83
|
+
}
|
|
84
|
+
function toRecord(row) {
|
|
85
|
+
return {
|
|
86
|
+
name: row.name,
|
|
87
|
+
executedAt: row.executed_at,
|
|
88
|
+
status: row.status,
|
|
89
|
+
error: row.error ?? void 0
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function ensureTable(adapter, tableName) {
|
|
93
|
+
if (ensuredTables.has(tableName)) return;
|
|
94
|
+
const table = quoteIdentifier(tableName);
|
|
95
|
+
await adapter.query(
|
|
96
|
+
`CREATE TABLE IF NOT EXISTS ${table} (
|
|
97
|
+
name TEXT PRIMARY KEY,
|
|
98
|
+
executed_at TIMESTAMPTZ NOT NULL,
|
|
99
|
+
status TEXT NOT NULL,
|
|
100
|
+
error TEXT
|
|
101
|
+
)`
|
|
102
|
+
);
|
|
103
|
+
ensuredTables.add(tableName);
|
|
104
|
+
}
|
|
105
|
+
async function getExecutedSeeders(adapter, tableName) {
|
|
106
|
+
const table = quoteIdentifier(tableName);
|
|
107
|
+
const rows = await adapter.query(
|
|
108
|
+
`SELECT name, executed_at, status, error FROM ${table} WHERE status = 'success'`
|
|
109
|
+
);
|
|
110
|
+
return rows.map(toRecord);
|
|
111
|
+
}
|
|
112
|
+
async function getAllTrackedSeeders(adapter, tableName) {
|
|
113
|
+
const table = quoteIdentifier(tableName);
|
|
114
|
+
const rows = await adapter.query(
|
|
115
|
+
`SELECT name, executed_at, status, error FROM ${table}`
|
|
116
|
+
);
|
|
117
|
+
return rows.map(toRecord);
|
|
118
|
+
}
|
|
119
|
+
async function upsertSeederRecord(adapter, tableName, record) {
|
|
120
|
+
const table = quoteIdentifier(tableName);
|
|
121
|
+
await adapter.query(
|
|
122
|
+
`INSERT INTO ${table} (name, executed_at, status, error)
|
|
123
|
+
VALUES ($1, $2, $3, $4)
|
|
124
|
+
ON CONFLICT (name) DO UPDATE
|
|
125
|
+
SET executed_at = EXCLUDED.executed_at,
|
|
126
|
+
status = EXCLUDED.status,
|
|
127
|
+
error = EXCLUDED.error`,
|
|
128
|
+
[record.name, record.executedAt, record.status, record.error ?? null]
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
async function deleteSeederRecord(adapter, tableName, name) {
|
|
132
|
+
const table = quoteIdentifier(tableName);
|
|
133
|
+
await adapter.query(`DELETE FROM ${table} WHERE name = $1`, [name]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/runner.ts
|
|
137
|
+
function requireAdapter(config) {
|
|
138
|
+
if (!config.adapter) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
"pg-seed-kit: an adapter is required. Pass { adapter } to the runner, or expose one from your config (the CLI builds it via `connect`)."
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return config.adapter;
|
|
144
|
+
}
|
|
145
|
+
function getSeederFiles(config) {
|
|
146
|
+
if (!fs2.existsSync(config.seedersPath)) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
return fs2.readdirSync(config.seedersPath).filter((f) => config.filePattern.test(f)).sort();
|
|
150
|
+
}
|
|
151
|
+
async function loadSeeder(name, filepath) {
|
|
152
|
+
const seederModule = await dynamicImport(pathToFileURL2(filepath).href);
|
|
153
|
+
const seederFn = seederModule.default ?? seederModule;
|
|
154
|
+
if (typeof seederFn !== "function") {
|
|
155
|
+
throw new Error(`Seeder "${name}" does not export a function`);
|
|
156
|
+
}
|
|
157
|
+
return seederFn;
|
|
158
|
+
}
|
|
159
|
+
async function runSingleSeeder(adapter, name, filepath, tableName, results) {
|
|
160
|
+
try {
|
|
161
|
+
const seederFn = await loadSeeder(name, filepath);
|
|
162
|
+
await seederFn();
|
|
163
|
+
await upsertSeederRecord(adapter, tableName, {
|
|
164
|
+
name,
|
|
165
|
+
executedAt: /* @__PURE__ */ new Date(),
|
|
166
|
+
status: "success"
|
|
167
|
+
});
|
|
168
|
+
results.push({ name, status: "success" });
|
|
169
|
+
console.log(`[pg-seed-kit] "${name}" ran successfully`);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const errorMessage = err?.message || String(err);
|
|
172
|
+
await upsertSeederRecord(adapter, tableName, {
|
|
173
|
+
name,
|
|
174
|
+
executedAt: /* @__PURE__ */ new Date(),
|
|
175
|
+
status: "failed",
|
|
176
|
+
error: errorMessage
|
|
177
|
+
});
|
|
178
|
+
results.push({ name, status: "failed", error: errorMessage });
|
|
179
|
+
console.error(`[pg-seed-kit] "${name}" failed: ${errorMessage}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function runPendingSeeders(overrides) {
|
|
183
|
+
const config = await loadConfig(overrides);
|
|
184
|
+
const adapter = requireAdapter(config);
|
|
185
|
+
await ensureTable(adapter, config.tableName);
|
|
186
|
+
const files = getSeederFiles(config);
|
|
187
|
+
const executed = await getExecutedSeeders(adapter, config.tableName);
|
|
188
|
+
const executedNames = new Set(executed.map((s) => s.name));
|
|
189
|
+
const results = [];
|
|
190
|
+
for (const file of files) {
|
|
191
|
+
const name = path2.basename(file, path2.extname(file));
|
|
192
|
+
if (executedNames.has(name)) {
|
|
193
|
+
results.push({ name, status: "skipped" });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const filepath = path2.join(config.seedersPath, file);
|
|
197
|
+
await runSingleSeeder(adapter, name, filepath, config.tableName, results);
|
|
198
|
+
}
|
|
199
|
+
return results;
|
|
200
|
+
}
|
|
201
|
+
async function runSeederByName(seederName, overrides) {
|
|
202
|
+
const config = await loadConfig(overrides);
|
|
203
|
+
const adapter = requireAdapter(config);
|
|
204
|
+
await ensureTable(adapter, config.tableName);
|
|
205
|
+
const files = getSeederFiles(config);
|
|
206
|
+
const file = files.find((f) => path2.basename(f, path2.extname(f)) === seederName);
|
|
207
|
+
if (!file) {
|
|
208
|
+
throw new Error(`Seeder "${seederName}" not found`);
|
|
209
|
+
}
|
|
210
|
+
const results = [];
|
|
211
|
+
const filepath = path2.join(config.seedersPath, file);
|
|
212
|
+
await runSingleSeeder(adapter, seederName, filepath, config.tableName, results);
|
|
213
|
+
return results;
|
|
214
|
+
}
|
|
215
|
+
async function getSeederStatuses(overrides) {
|
|
216
|
+
const config = await loadConfig(overrides);
|
|
217
|
+
const adapter = requireAdapter(config);
|
|
218
|
+
await ensureTable(adapter, config.tableName);
|
|
219
|
+
const files = getSeederFiles(config);
|
|
220
|
+
const tracked = await getAllTrackedSeeders(adapter, config.tableName);
|
|
221
|
+
const trackedMap = new Map(tracked.map((s) => [s.name, s]));
|
|
222
|
+
return files.map((f) => {
|
|
223
|
+
const name = path2.basename(f, path2.extname(f));
|
|
224
|
+
const record = trackedMap.get(name);
|
|
225
|
+
return {
|
|
226
|
+
name,
|
|
227
|
+
status: record?.status ?? "pending",
|
|
228
|
+
executedAt: record?.executedAt ?? null,
|
|
229
|
+
error: record?.error ?? null
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async function resetSeeder(seederName, overrides) {
|
|
234
|
+
const config = await loadConfig(overrides);
|
|
235
|
+
const adapter = requireAdapter(config);
|
|
236
|
+
await ensureTable(adapter, config.tableName);
|
|
237
|
+
await deleteSeederRecord(adapter, config.tableName, seederName);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export {
|
|
241
|
+
loadConfig,
|
|
242
|
+
resolveSeedersPath,
|
|
243
|
+
runPendingSeeders,
|
|
244
|
+
runSeederByName,
|
|
245
|
+
getSeederStatuses,
|
|
246
|
+
resetSeeder
|
|
247
|
+
};
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// src/config.ts
|
|
26
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
27
|
+
var import_path = __toESM(require("path"), 1);
|
|
28
|
+
var import_url = require("url");
|
|
29
|
+
|
|
30
|
+
// src/dynamic-import.ts
|
|
31
|
+
var dynamicImport = new Function("specifier", "return import(specifier);");
|
|
32
|
+
|
|
33
|
+
// src/config.ts
|
|
34
|
+
var DEFAULT_TABLE_NAME = "seeders";
|
|
35
|
+
var DEFAULT_FILE_PATTERN = /^\d{14}-.+\.seeder\.(ts|js|mjs|cjs)$/;
|
|
36
|
+
var CONFIG_FILES = ["pg-seed-kit.config.js", "pg-seed-kit.config.cjs", "pg-seed-kit.config.mjs"];
|
|
37
|
+
async function loadConfigFromFile(cwd) {
|
|
38
|
+
for (const filename of CONFIG_FILES) {
|
|
39
|
+
const filepath = import_path.default.resolve(cwd, filename);
|
|
40
|
+
if (import_fs.default.existsSync(filepath)) {
|
|
41
|
+
const loaded = await dynamicImport((0, import_url.pathToFileURL)(filepath).href);
|
|
42
|
+
return loaded.default ?? loaded;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const pkgPath = import_path.default.resolve(cwd, "package.json");
|
|
46
|
+
if (import_fs.default.existsSync(pkgPath)) {
|
|
47
|
+
const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
48
|
+
if (pkg["pg-seed-kit"]) {
|
|
49
|
+
return pkg["pg-seed-kit"];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function isResolver(value) {
|
|
55
|
+
return typeof value === "function";
|
|
56
|
+
}
|
|
57
|
+
async function loadConfig(overrides) {
|
|
58
|
+
const cwd = process.cwd();
|
|
59
|
+
const fileConfig = await loadConfigFromFile(cwd);
|
|
60
|
+
const merged = { ...fileConfig, ...overrides };
|
|
61
|
+
if (!merged.seedersPath) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
'pg-seed-kit: "seedersPath" is required. Provide it via pg-seed-kit.config.js, package.json, or inline config.'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const rawPath = isResolver(merged.seedersPath) ? merged.seedersPath() : merged.seedersPath;
|
|
67
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) {
|
|
68
|
+
throw new Error('pg-seed-kit: "seedersPath" must resolve to a non-empty string.');
|
|
69
|
+
}
|
|
70
|
+
const seedersPath = import_path.default.isAbsolute(rawPath) ? rawPath : import_path.default.resolve(cwd, rawPath);
|
|
71
|
+
return {
|
|
72
|
+
seedersPath,
|
|
73
|
+
tableName: merged.tableName ?? DEFAULT_TABLE_NAME,
|
|
74
|
+
filePattern: merged.filePattern ?? DEFAULT_FILE_PATTERN,
|
|
75
|
+
adapter: merged.adapter,
|
|
76
|
+
connect: merged.connect
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/runner.ts
|
|
81
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
82
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
83
|
+
var import_url2 = require("url");
|
|
84
|
+
|
|
85
|
+
// src/tracker.ts
|
|
86
|
+
var ensuredTables = /* @__PURE__ */ new Set();
|
|
87
|
+
function quoteIdentifier(name) {
|
|
88
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`pg-seed-kit: invalid table name "${name}". Use only letters, digits, and underscores (must not start with a digit).`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return `"${name}"`;
|
|
94
|
+
}
|
|
95
|
+
function toRecord(row) {
|
|
96
|
+
return {
|
|
97
|
+
name: row.name,
|
|
98
|
+
executedAt: row.executed_at,
|
|
99
|
+
status: row.status,
|
|
100
|
+
error: row.error ?? void 0
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function ensureTable(adapter, tableName) {
|
|
104
|
+
if (ensuredTables.has(tableName)) return;
|
|
105
|
+
const table = quoteIdentifier(tableName);
|
|
106
|
+
await adapter.query(
|
|
107
|
+
`CREATE TABLE IF NOT EXISTS ${table} (
|
|
108
|
+
name TEXT PRIMARY KEY,
|
|
109
|
+
executed_at TIMESTAMPTZ NOT NULL,
|
|
110
|
+
status TEXT NOT NULL,
|
|
111
|
+
error TEXT
|
|
112
|
+
)`
|
|
113
|
+
);
|
|
114
|
+
ensuredTables.add(tableName);
|
|
115
|
+
}
|
|
116
|
+
async function getExecutedSeeders(adapter, tableName) {
|
|
117
|
+
const table = quoteIdentifier(tableName);
|
|
118
|
+
const rows = await adapter.query(
|
|
119
|
+
`SELECT name, executed_at, status, error FROM ${table} WHERE status = 'success'`
|
|
120
|
+
);
|
|
121
|
+
return rows.map(toRecord);
|
|
122
|
+
}
|
|
123
|
+
async function getAllTrackedSeeders(adapter, tableName) {
|
|
124
|
+
const table = quoteIdentifier(tableName);
|
|
125
|
+
const rows = await adapter.query(
|
|
126
|
+
`SELECT name, executed_at, status, error FROM ${table}`
|
|
127
|
+
);
|
|
128
|
+
return rows.map(toRecord);
|
|
129
|
+
}
|
|
130
|
+
async function upsertSeederRecord(adapter, tableName, record) {
|
|
131
|
+
const table = quoteIdentifier(tableName);
|
|
132
|
+
await adapter.query(
|
|
133
|
+
`INSERT INTO ${table} (name, executed_at, status, error)
|
|
134
|
+
VALUES ($1, $2, $3, $4)
|
|
135
|
+
ON CONFLICT (name) DO UPDATE
|
|
136
|
+
SET executed_at = EXCLUDED.executed_at,
|
|
137
|
+
status = EXCLUDED.status,
|
|
138
|
+
error = EXCLUDED.error`,
|
|
139
|
+
[record.name, record.executedAt, record.status, record.error ?? null]
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
async function deleteSeederRecord(adapter, tableName, name) {
|
|
143
|
+
const table = quoteIdentifier(tableName);
|
|
144
|
+
await adapter.query(`DELETE FROM ${table} WHERE name = $1`, [name]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/runner.ts
|
|
148
|
+
function requireAdapter(config) {
|
|
149
|
+
if (!config.adapter) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"pg-seed-kit: an adapter is required. Pass { adapter } to the runner, or expose one from your config (the CLI builds it via `connect`)."
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return config.adapter;
|
|
155
|
+
}
|
|
156
|
+
function getSeederFiles(config) {
|
|
157
|
+
if (!import_fs2.default.existsSync(config.seedersPath)) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
return import_fs2.default.readdirSync(config.seedersPath).filter((f) => config.filePattern.test(f)).sort();
|
|
161
|
+
}
|
|
162
|
+
async function loadSeeder(name, filepath) {
|
|
163
|
+
const seederModule = await dynamicImport((0, import_url2.pathToFileURL)(filepath).href);
|
|
164
|
+
const seederFn = seederModule.default ?? seederModule;
|
|
165
|
+
if (typeof seederFn !== "function") {
|
|
166
|
+
throw new Error(`Seeder "${name}" does not export a function`);
|
|
167
|
+
}
|
|
168
|
+
return seederFn;
|
|
169
|
+
}
|
|
170
|
+
async function runSingleSeeder(adapter, name, filepath, tableName, results) {
|
|
171
|
+
try {
|
|
172
|
+
const seederFn = await loadSeeder(name, filepath);
|
|
173
|
+
await seederFn();
|
|
174
|
+
await upsertSeederRecord(adapter, tableName, {
|
|
175
|
+
name,
|
|
176
|
+
executedAt: /* @__PURE__ */ new Date(),
|
|
177
|
+
status: "success"
|
|
178
|
+
});
|
|
179
|
+
results.push({ name, status: "success" });
|
|
180
|
+
console.log(`[pg-seed-kit] "${name}" ran successfully`);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
const errorMessage = err?.message || String(err);
|
|
183
|
+
await upsertSeederRecord(adapter, tableName, {
|
|
184
|
+
name,
|
|
185
|
+
executedAt: /* @__PURE__ */ new Date(),
|
|
186
|
+
status: "failed",
|
|
187
|
+
error: errorMessage
|
|
188
|
+
});
|
|
189
|
+
results.push({ name, status: "failed", error: errorMessage });
|
|
190
|
+
console.error(`[pg-seed-kit] "${name}" failed: ${errorMessage}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function runPendingSeeders(overrides) {
|
|
194
|
+
const config = await loadConfig(overrides);
|
|
195
|
+
const adapter = requireAdapter(config);
|
|
196
|
+
await ensureTable(adapter, config.tableName);
|
|
197
|
+
const files = getSeederFiles(config);
|
|
198
|
+
const executed = await getExecutedSeeders(adapter, config.tableName);
|
|
199
|
+
const executedNames = new Set(executed.map((s) => s.name));
|
|
200
|
+
const results = [];
|
|
201
|
+
for (const file of files) {
|
|
202
|
+
const name = import_path2.default.basename(file, import_path2.default.extname(file));
|
|
203
|
+
if (executedNames.has(name)) {
|
|
204
|
+
results.push({ name, status: "skipped" });
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const filepath = import_path2.default.join(config.seedersPath, file);
|
|
208
|
+
await runSingleSeeder(adapter, name, filepath, config.tableName, results);
|
|
209
|
+
}
|
|
210
|
+
return results;
|
|
211
|
+
}
|
|
212
|
+
async function runSeederByName(seederName, overrides) {
|
|
213
|
+
const config = await loadConfig(overrides);
|
|
214
|
+
const adapter = requireAdapter(config);
|
|
215
|
+
await ensureTable(adapter, config.tableName);
|
|
216
|
+
const files = getSeederFiles(config);
|
|
217
|
+
const file = files.find((f) => import_path2.default.basename(f, import_path2.default.extname(f)) === seederName);
|
|
218
|
+
if (!file) {
|
|
219
|
+
throw new Error(`Seeder "${seederName}" not found`);
|
|
220
|
+
}
|
|
221
|
+
const results = [];
|
|
222
|
+
const filepath = import_path2.default.join(config.seedersPath, file);
|
|
223
|
+
await runSingleSeeder(adapter, seederName, filepath, config.tableName, results);
|
|
224
|
+
return results;
|
|
225
|
+
}
|
|
226
|
+
async function getSeederStatuses(overrides) {
|
|
227
|
+
const config = await loadConfig(overrides);
|
|
228
|
+
const adapter = requireAdapter(config);
|
|
229
|
+
await ensureTable(adapter, config.tableName);
|
|
230
|
+
const files = getSeederFiles(config);
|
|
231
|
+
const tracked = await getAllTrackedSeeders(adapter, config.tableName);
|
|
232
|
+
const trackedMap = new Map(tracked.map((s) => [s.name, s]));
|
|
233
|
+
return files.map((f) => {
|
|
234
|
+
const name = import_path2.default.basename(f, import_path2.default.extname(f));
|
|
235
|
+
const record = trackedMap.get(name);
|
|
236
|
+
return {
|
|
237
|
+
name,
|
|
238
|
+
status: record?.status ?? "pending",
|
|
239
|
+
executedAt: record?.executedAt ?? null,
|
|
240
|
+
error: record?.error ?? null
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
async function resetSeeder(seederName, overrides) {
|
|
245
|
+
const config = await loadConfig(overrides);
|
|
246
|
+
const adapter = requireAdapter(config);
|
|
247
|
+
await ensureTable(adapter, config.tableName);
|
|
248
|
+
await deleteSeederRecord(adapter, config.tableName, seederName);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/cli/create.ts
|
|
252
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
253
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
254
|
+
var TEMPLATE = `const seed = async (): Promise<void> => {
|
|
255
|
+
// TODO: implement this seeder.
|
|
256
|
+
// Keep it idempotent so re-runs are safe: check-before-insert,
|
|
257
|
+
// or \`INSERT ... ON CONFLICT DO NOTHING\`.
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export default seed;
|
|
261
|
+
`;
|
|
262
|
+
function generateTimestamp() {
|
|
263
|
+
const now = /* @__PURE__ */ new Date();
|
|
264
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
265
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
266
|
+
}
|
|
267
|
+
async function createSeeder(name) {
|
|
268
|
+
const config = await loadConfig();
|
|
269
|
+
const timestamp = generateTimestamp();
|
|
270
|
+
const filename = `${timestamp}-${name}.seeder.ts`;
|
|
271
|
+
const filepath = import_path3.default.join(config.seedersPath, filename);
|
|
272
|
+
if (!import_fs3.default.existsSync(config.seedersPath)) {
|
|
273
|
+
import_fs3.default.mkdirSync(config.seedersPath, { recursive: true });
|
|
274
|
+
}
|
|
275
|
+
import_fs3.default.writeFileSync(filepath, TEMPLATE);
|
|
276
|
+
console.log(`Created: ${filepath}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/cli/commands.ts
|
|
280
|
+
function printUsage() {
|
|
281
|
+
console.log("Usage:");
|
|
282
|
+
console.log(" pg-seed-kit create <name> Create a new seeder file");
|
|
283
|
+
console.log(" pg-seed-kit status List seeders and statuses");
|
|
284
|
+
console.log(" pg-seed-kit run [name] Run pending seeders or one seeder");
|
|
285
|
+
console.log(" pg-seed-kit reset <name> Mark a seeder as pending");
|
|
286
|
+
console.log("");
|
|
287
|
+
console.log("Connection: your config must export an async connect() that returns an adapter.");
|
|
288
|
+
}
|
|
289
|
+
async function withAdapter(action) {
|
|
290
|
+
const config = await loadConfig();
|
|
291
|
+
if (!config.connect) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
"pg-seed-kit: this command needs a database connection. Export an async connect() from your config that returns an adapter."
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
const adapter = await config.connect();
|
|
297
|
+
try {
|
|
298
|
+
return await action({ adapter });
|
|
299
|
+
} finally {
|
|
300
|
+
await adapter.close?.();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function printStatuses(statuses) {
|
|
304
|
+
if (statuses.length === 0) {
|
|
305
|
+
console.log("No seeders found.");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
for (const status of statuses) {
|
|
309
|
+
const executedAt = status.executedAt ? ` ${status.executedAt.toISOString()}` : "";
|
|
310
|
+
const error = status.error ? ` - ${status.error}` : "";
|
|
311
|
+
console.log(`${status.status.padEnd(7)} ${status.name}${executedAt}${error}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function printRunResults(results) {
|
|
315
|
+
if (results.length === 0) {
|
|
316
|
+
console.log("No seeders found.");
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
for (const result of results) {
|
|
320
|
+
const error = result.error ? ` - ${result.error}` : "";
|
|
321
|
+
console.log(`${result.status.padEnd(7)} ${result.name}${error}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function runCli(args = process.argv.slice(2)) {
|
|
325
|
+
const command = args[0];
|
|
326
|
+
if (!command || command === "--help" || command === "-h") {
|
|
327
|
+
printUsage();
|
|
328
|
+
return command ? 0 : 1;
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
switch (command) {
|
|
332
|
+
case "create": {
|
|
333
|
+
const name = args[1];
|
|
334
|
+
if (!name) {
|
|
335
|
+
console.error("Error: seeder name is required.");
|
|
336
|
+
console.error("Usage: pg-seed-kit create <name>");
|
|
337
|
+
return 1;
|
|
338
|
+
}
|
|
339
|
+
await createSeeder(name);
|
|
340
|
+
return 0;
|
|
341
|
+
}
|
|
342
|
+
case "status": {
|
|
343
|
+
await withAdapter(async (overrides) => {
|
|
344
|
+
const statuses = await getSeederStatuses(overrides);
|
|
345
|
+
printStatuses(statuses);
|
|
346
|
+
});
|
|
347
|
+
return 0;
|
|
348
|
+
}
|
|
349
|
+
case "run": {
|
|
350
|
+
let results = [];
|
|
351
|
+
const name = args[1];
|
|
352
|
+
await withAdapter(async (overrides) => {
|
|
353
|
+
results = name ? await runSeederByName(name, overrides) : await runPendingSeeders(overrides);
|
|
354
|
+
printRunResults(results);
|
|
355
|
+
});
|
|
356
|
+
return results.some((result) => result.status === "failed") ? 1 : 0;
|
|
357
|
+
}
|
|
358
|
+
case "reset": {
|
|
359
|
+
const name = args[1];
|
|
360
|
+
if (!name) {
|
|
361
|
+
console.error("Error: seeder name is required.");
|
|
362
|
+
console.error("Usage: pg-seed-kit reset <name>");
|
|
363
|
+
return 1;
|
|
364
|
+
}
|
|
365
|
+
await withAdapter(async (overrides) => {
|
|
366
|
+
await resetSeeder(name, overrides);
|
|
367
|
+
console.log(`Reset: ${name}`);
|
|
368
|
+
});
|
|
369
|
+
return 0;
|
|
370
|
+
}
|
|
371
|
+
default:
|
|
372
|
+
console.error(`Unknown command: "${command}"`);
|
|
373
|
+
printUsage();
|
|
374
|
+
return 1;
|
|
375
|
+
}
|
|
376
|
+
} catch (err) {
|
|
377
|
+
console.error(err?.message || String(err));
|
|
378
|
+
return 1;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/cli/index.ts
|
|
383
|
+
runCli().then((exitCode) => {
|
|
384
|
+
process.exitCode = exitCode;
|
|
385
|
+
});
|