@payload-cc/payload-collection-cli 1.2.0 → 1.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/README.md CHANGED
@@ -1,8 +1,25 @@
1
1
  # payload-collection-cli
2
2
 
3
- Functional CLI for Payload 3.0 collection management.
3
+ **The missing CLI for Payload 3.0. Manage collections without writing boilerplate scripts.**
4
4
 
5
- Allows for magical, easy-to-use bulk imports, updates, deletions and operations on Payload CMS collections using simple JSONLines or JSON files, with automatic relation resolution.
5
+ ---
6
+
7
+ ## Why?
8
+
9
+ Payload 3.0 is powerful, but performing simple data operations often requires writing one-off scripts. **payload-collection-cli** turns your terminal into a direct interface for your collections, supporting intelligent relation lookups by name/slug instead of cryptic IDs.
10
+
11
+ ## Features
12
+
13
+ - ⚡️ **Zero Scripting:** Run CRUD operations directly from your terminal.
14
+ - 🔍 **Relation Lookup:** Resolve relationships using `name`, `email`, or `slug`.
15
+ - 📄 **Batch Processing:** Support for both JSON strings and JSONLines (`.jsonl`) files.
16
+ - ⚙️ **Configurable:** Define your lookup logic in a simple external config.
17
+ - 🛡 **Native Performance:** Uses Local API to ensure all Hooks and Validations run.
18
+
19
+ ## 📖 Documentation
20
+
21
+ - **🚀 [Official Guide](https://payload-cc.github.io/payload-collection-cli/)**
22
+ - **🤖 [AI Context Page](https://payload-cc.github.io/payload-collection-cli/ai.html)** (One-file summary for coding agents)
6
23
 
7
24
  ## Installation
8
25
 
@@ -17,7 +34,7 @@ You can immediately start using the commands without any configuration for basic
17
34
 
18
35
  **Command Syntax**:
19
36
  ```bash
20
- npx @payload-cc/payload-collection-cli [-c config-file] <collection-slug> <operation> <file or string>
37
+ npx @payload-cc/payload-collection-cli [options] <collection-slug> <operation> <file or string>
21
38
  ```
22
39
 
23
40
  **Examples**:
@@ -39,33 +56,8 @@ npx @payload-cc/payload-collection-cli -c ./my-map.config.ts users upsert data.j
39
56
  - `upsert`: Update if existing, create if not.
40
57
 
41
58
  ## Specifications & FAQ
42
- For detailed behavior specifications (such as how identifier lookups work strictly during upserts, updates, and deletes), please refer to the [Specifications & FAQ](docs/references/specs_detail.md).
43
-
44
- ## Configuration (Optional)
45
-
46
- ### Relation Mappings (`payload-collection-cli.config.ts`)
47
- By default, Payload relations require you to provide target document IDs (e.g., ObjectIDs or numeric IDs). The CLI can magically resolve these relations by searching for human-readable fields instead.
48
-
49
- **Example Scenario**:
50
- Assume your `posts` collection has a relationship field named `author` that references the `users` collection. Instead of manually finding and hard-coding the user's database ID, you want to simply provide their email address.
51
-
52
- Create a `payload-collection-cli.config.ts` in your Payload project root (or pass it via `-c`) to define the `lookupField` for the `users` collection:
53
-
54
- ```typescript
55
- export const cliConfig = {
56
- mappings: {
57
- users: {
58
- lookupField: 'email',
59
- onNotFound: 'error',
60
- },
61
- posts: {
62
- lookupField: 'slug'
63
- }
64
- }
65
- }
66
- ```
67
59
 
68
- Now, when you supply an `author: "user@example.com"` property to a `posts` collection insertion, the CLI will intercept this relationship, look up the `users` collection by the `email` field, and automatically replace the email string with the actual database ID before inserting!
60
+ For detailed configuration options (including `package.json` defaults, relation mappings, `onNotFound` behaviors, and cross-file imports), please refer to the [Specifications & FAQ](docs/references/specs_detail.md).
69
61
 
70
62
  ## Development & CI/CD
71
63
 
package/dist/bin.cjs ADDED
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
27
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
28
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
29
+
30
+ // src/bin.ts
31
+ var import_payload = require("payload");
32
+ var import_jiti = require("jiti");
33
+ var import_path2 = __toESM(require("path"), 1);
34
+ var import_fs2 = __toESM(require("fs"), 1);
35
+ var import_yargs = __toESM(require("yargs/yargs"), 1);
36
+ var import_helpers = require("yargs/helpers");
37
+ var import_zod = require("zod");
38
+
39
+ // src/executor.ts
40
+ var import_fs = __toESM(require("fs"), 1);
41
+ var import_readline = __toESM(require("readline"), 1);
42
+ var import_path = __toESM(require("path"), 1);
43
+
44
+ // src/resolver.ts
45
+ async function resolveRelations(payload, collectionSlug, data, config) {
46
+ const collection = payload.collections[collectionSlug];
47
+ if (!collection) throw new Error(`Collection "${collectionSlug}" not found.`);
48
+ const resolved = { ...data };
49
+ const mappingConfig = config.mappings[collectionSlug];
50
+ if (mappingConfig?.defaults) {
51
+ for (const [k, v] of Object.entries(mappingConfig.defaults)) {
52
+ if (resolved[k] === void 0 || resolved[k] === null) {
53
+ resolved[k] = v;
54
+ }
55
+ }
56
+ }
57
+ for (const field of collection.config.fields) {
58
+ if (field.type === "relationship" && resolved[field.name]) {
59
+ const relationTo = Array.isArray(field.relationTo) ? field.relationTo[0] : field.relationTo;
60
+ const mapping = config.mappings[relationTo];
61
+ if (!mapping) continue;
62
+ const rawValue = resolved[field.name];
63
+ const isArray = Array.isArray(rawValue);
64
+ const values = isArray ? rawValue : [rawValue];
65
+ const resolvedIds = [];
66
+ for (const val of values) {
67
+ if (typeof val === "string" && val.length < 24) {
68
+ const found = await payload.find({
69
+ collection: relationTo,
70
+ where: { [mapping.lookupField]: { equals: val } },
71
+ limit: 1
72
+ });
73
+ if (found.docs.length > 0) {
74
+ resolvedIds.push(found.docs[0].id);
75
+ } else if (mapping.onNotFound === "create") {
76
+ const created = await payload.create({
77
+ collection: relationTo,
78
+ data: { [mapping.lookupField]: val }
79
+ });
80
+ resolvedIds.push(created.id);
81
+ } else if (mapping.onNotFound === "error") {
82
+ throw new Error(`Relation not found: ${relationTo} (${mapping.lookupField}=${val})`);
83
+ } else {
84
+ resolvedIds.push(val);
85
+ }
86
+ } else {
87
+ resolvedIds.push(val);
88
+ }
89
+ }
90
+ resolved[field.name] = isArray ? resolvedIds : resolvedIds[0];
91
+ }
92
+ }
93
+ return resolved;
94
+ }
95
+
96
+ // src/executor.ts
97
+ async function processSingle(payload, collection, action, data, config) {
98
+ const mapping = config.mappings[collection];
99
+ const lookupField = mapping?.lookupField || "id";
100
+ const resolved = await resolveRelations(payload, collection, data, config);
101
+ switch (action) {
102
+ case "create":
103
+ return await payload.create({ collection, data: resolved });
104
+ case "upsert":
105
+ if (data[lookupField] === void 0) {
106
+ throw new Error(`[upsert] Missing lookup field '${lookupField}' in data. Cannot perform upsert.`);
107
+ }
108
+ const existingUpsert = await payload.find({
109
+ collection,
110
+ where: { [lookupField]: { equals: data[lookupField] } }
111
+ });
112
+ if (existingUpsert.docs.length > 0) {
113
+ return await payload.update({ collection, id: existingUpsert.docs[0].id, data: resolved });
114
+ }
115
+ return await payload.create({ collection, data: resolved });
116
+ case "update":
117
+ if (data[lookupField] === void 0) {
118
+ throw new Error(`[update] Missing lookup field '${lookupField}' in data. Cannot perform update.`);
119
+ }
120
+ return await payload.update({
121
+ collection,
122
+ where: { [lookupField]: { equals: data[lookupField] } },
123
+ data: resolved
124
+ });
125
+ case "delete":
126
+ const delVal = typeof data === "object" ? data[lookupField] : data;
127
+ if (delVal === void 0) {
128
+ throw new Error(`[delete] Missing lookup field '${lookupField}' in action. Cannot perform delete.`);
129
+ }
130
+ return await payload.delete({
131
+ collection,
132
+ where: { [lookupField]: { equals: delVal } }
133
+ });
134
+ default:
135
+ throw new Error(`Unsupported action: ${action}`);
136
+ }
137
+ }
138
+ async function execute(payload, collection, action, input, config) {
139
+ if (input.endsWith(".jsonl")) {
140
+ const filePath = import_path.default.resolve(process.cwd(), input);
141
+ const rl = import_readline.default.createInterface({ input: import_fs.default.createReadStream(filePath) });
142
+ for await (const line of rl) {
143
+ if (line.trim()) await processSingle(payload, collection, action, JSON.parse(line), config);
144
+ }
145
+ return { status: "bulk success" };
146
+ }
147
+ return await processSingle(payload, collection, action, JSON.parse(input), config);
148
+ }
149
+
150
+ // src/bin.ts
151
+ var jiti = (0, import_jiti.createJiti)(importMetaUrl, { moduleCache: false, fsCache: false });
152
+ var MappingConfigSchema = import_zod.z.object({
153
+ lookupField: import_zod.z.string().default("id"),
154
+ onNotFound: import_zod.z.enum(["error", "ignore", "create"]).default("error"),
155
+ defaults: import_zod.z.record(import_zod.z.string(), import_zod.z.any()).optional()
156
+ });
157
+ var CLIConfigSchema = import_zod.z.object({
158
+ mappings: import_zod.z.record(import_zod.z.string(), MappingConfigSchema).default({})
159
+ });
160
+ async function run() {
161
+ console.log("\u{1F3C1} Starting CLI...");
162
+ const root = process.cwd();
163
+ const argv = await (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).usage("Usage: $0 [options] <collection-slug> <operation> <file or string>").options({
164
+ "config-file": {
165
+ alias: "c",
166
+ type: "string",
167
+ describe: "Path to a configuration file (named export required)."
168
+ },
169
+ "config-json": {
170
+ alias: "j",
171
+ type: "string",
172
+ describe: "Inline JSON string for configuration."
173
+ },
174
+ "config-export-name": {
175
+ alias: "n",
176
+ type: "string",
177
+ describe: "The name of the export to use from the configuration file.",
178
+ default: "cliConfig"
179
+ }
180
+ }).pkgConf("payload-collection-cli").demandCommand(3, "Collection slug, operation, and input are required.").help().parse();
181
+ const { configFile, configJson, configExportName, _: [collection, action, input] } = argv;
182
+ let rawConfig = { mappings: {} };
183
+ if (configJson) {
184
+ try {
185
+ rawConfig = typeof configJson === "string" ? JSON.parse(configJson) : configJson;
186
+ } catch (err) {
187
+ console.error("\u274C Error: Failed to parse inline JSON config:", err);
188
+ process.exit(1);
189
+ }
190
+ } else if (configFile) {
191
+ const customConfigPath = import_path2.default.resolve(root, configFile);
192
+ if (!import_fs2.default.existsSync(customConfigPath)) {
193
+ console.error(`\u274C Error: Config file not found at ${customConfigPath}`);
194
+ process.exit(1);
195
+ }
196
+ const imported = await jiti.import(customConfigPath);
197
+ if (imported[configExportName]) {
198
+ rawConfig = imported[configExportName];
199
+ } else {
200
+ console.error(`\u274C Error: Named export "${configExportName}" not found in ${configFile}`);
201
+ process.exit(1);
202
+ }
203
+ }
204
+ const validation = CLIConfigSchema.safeParse(rawConfig);
205
+ if (!validation.success) {
206
+ console.error("\u274C Error: Invalid configuration structure:");
207
+ validation.error.issues.forEach((issue) => {
208
+ console.error(` - [${issue.path.join(".")}] ${issue.message}`);
209
+ });
210
+ process.exit(1);
211
+ }
212
+ const cliConfig = validation.data;
213
+ const configPath = [
214
+ import_path2.default.resolve(root, "src/payload.config.ts"),
215
+ import_path2.default.resolve(root, "payload.config.ts")
216
+ ].find((p) => import_fs2.default.existsSync(p));
217
+ if (!configPath) {
218
+ console.error("\u274C Error: payload.config.ts not found.");
219
+ process.exit(1);
220
+ }
221
+ const { default: payloadConfig } = await jiti.import(configPath);
222
+ const payload = await (0, import_payload.getPayload)({ config: payloadConfig });
223
+ console.log("\u2705 Connected to Payload");
224
+ try {
225
+ const result = await execute(payload, collection, action, input, cliConfig);
226
+ console.log("\u2728 Operation successful");
227
+ } catch (err) {
228
+ console.error("\u274C Error:", err.message);
229
+ process.exit(1);
230
+ }
231
+ process.exit(0);
232
+ }
233
+ run().catch((err) => {
234
+ console.error("\u274C Fatal Error:", err.message);
235
+ process.exit(1);
236
+ });
package/dist/bin.js CHANGED
@@ -1,192 +1,89 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") {
11
- for (let key of __getOwnPropNames(from))
12
- if (!__hasOwnProp.call(to, key) && key !== except)
13
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
- }
15
- return to;
16
- };
17
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
- // If the importer is in node compatibility mode or this is not an ESM
19
- // file that has been converted to a CommonJS file using a Babel-
20
- // compatible transform (i.e. "__esModule" has not been set), then set
21
- // "default" to the CommonJS "module.exports" for node compatibility.
22
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
- mod
24
- ));
25
-
26
- // node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
27
- var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
28
- var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
2
+ import {
3
+ execute
4
+ } from "./chunk-D5RS7XAO.js";
29
5
 
30
6
  // src/bin.ts
31
- var import_payload = require("payload");
32
- var import_jiti = require("jiti");
33
- var import_path2 = __toESM(require("path"));
34
-
35
- // src/executor.ts
36
- var import_fs = __toESM(require("fs"));
37
- var import_readline = __toESM(require("readline"));
38
- var import_path = __toESM(require("path"));
39
-
40
- // src/resolver.ts
41
- async function resolveRelations(payload, collectionSlug, data, config) {
42
- const collection = payload.collections[collectionSlug];
43
- if (!collection) throw new Error(`Collection "${collectionSlug}" not found.`);
44
- const resolved = { ...data };
45
- const mappingConfig = config.mappings[collectionSlug];
46
- if (mappingConfig?.defaults) {
47
- for (const [k, v] of Object.entries(mappingConfig.defaults)) {
48
- if (resolved[k] === void 0 || resolved[k] === null) {
49
- resolved[k] = v;
50
- }
51
- }
52
- }
53
- for (const field of collection.config.fields) {
54
- if (field.type === "relationship" && resolved[field.name]) {
55
- const relationTo = Array.isArray(field.relationTo) ? field.relationTo[0] : field.relationTo;
56
- const mapping = config.mappings[relationTo];
57
- if (!mapping) continue;
58
- const rawValue = resolved[field.name];
59
- const isArray = Array.isArray(rawValue);
60
- const values = isArray ? rawValue : [rawValue];
61
- const resolvedIds = [];
62
- for (const val of values) {
63
- if (typeof val === "string" && val.length < 24) {
64
- const found = await payload.find({
65
- collection: relationTo,
66
- where: { [mapping.lookupField]: { equals: val } },
67
- limit: 1
68
- });
69
- if (found.docs.length > 0) {
70
- resolvedIds.push(found.docs[0].id);
71
- } else if (mapping.onNotFound === "create") {
72
- const created = await payload.create({
73
- collection: relationTo,
74
- data: { [mapping.lookupField]: val }
75
- });
76
- resolvedIds.push(created.id);
77
- } else if (mapping.onNotFound === "error") {
78
- throw new Error(`Relation not found: ${relationTo} (${mapping.lookupField}=${val})`);
79
- } else {
80
- resolvedIds.push(val);
81
- }
82
- } else {
83
- resolvedIds.push(val);
84
- }
85
- }
86
- resolved[field.name] = isArray ? resolvedIds : resolvedIds[0];
7
+ import { getPayload } from "payload";
8
+ import { createJiti } from "jiti";
9
+ import path from "path";
10
+ import fs from "fs";
11
+ import yargs from "yargs/yargs";
12
+ import { hideBin } from "yargs/helpers";
13
+ import { z } from "zod";
14
+ var jiti = createJiti(import.meta.url, { moduleCache: false, fsCache: false });
15
+ var MappingConfigSchema = z.object({
16
+ lookupField: z.string().default("id"),
17
+ onNotFound: z.enum(["error", "ignore", "create"]).default("error"),
18
+ defaults: z.record(z.string(), z.any()).optional()
19
+ });
20
+ var CLIConfigSchema = z.object({
21
+ mappings: z.record(z.string(), MappingConfigSchema).default({})
22
+ });
23
+ async function run() {
24
+ console.log("\u{1F3C1} Starting CLI...");
25
+ const root = process.cwd();
26
+ const argv = await yargs(hideBin(process.argv)).usage("Usage: $0 [options] <collection-slug> <operation> <file or string>").options({
27
+ "config-file": {
28
+ alias: "c",
29
+ type: "string",
30
+ describe: "Path to a configuration file (named export required)."
31
+ },
32
+ "config-json": {
33
+ alias: "j",
34
+ type: "string",
35
+ describe: "Inline JSON string for configuration."
36
+ },
37
+ "config-export-name": {
38
+ alias: "n",
39
+ type: "string",
40
+ describe: "The name of the export to use from the configuration file.",
41
+ default: "cliConfig"
87
42
  }
88
- }
89
- return resolved;
90
- }
91
-
92
- // src/executor.ts
93
- async function processSingle(payload, collection, action, data, config) {
94
- const mapping = config.mappings[collection];
95
- const lookupField = mapping?.lookupField || "id";
96
- const resolved = await resolveRelations(payload, collection, data, config);
97
- switch (action) {
98
- case "create":
99
- return await payload.create({ collection, data: resolved });
100
- case "upsert":
101
- if (data[lookupField] === void 0) {
102
- throw new Error(`[upsert] Missing lookup field '${lookupField}' in data. Cannot perform upsert.`);
103
- }
104
- const existingUpsert = await payload.find({
105
- collection,
106
- where: { [lookupField]: { equals: data[lookupField] } }
107
- });
108
- if (existingUpsert.docs.length > 0) {
109
- return await payload.update({ collection, id: existingUpsert.docs[0].id, data: resolved });
110
- }
111
- return await payload.create({ collection, data: resolved });
112
- case "update":
113
- if (data[lookupField] === void 0) {
114
- throw new Error(`[update] Missing lookup field '${lookupField}' in data. Cannot perform update.`);
115
- }
116
- return await payload.update({
117
- collection,
118
- where: { [lookupField]: { equals: data[lookupField] } },
119
- data: resolved
120
- });
121
- case "delete":
122
- const delVal = typeof data === "object" ? data[lookupField] : data;
123
- if (delVal === void 0) {
124
- throw new Error(`[delete] Missing lookup field '${lookupField}' in action. Cannot perform delete.`);
125
- }
126
- return await payload.delete({
127
- collection,
128
- where: { [lookupField]: { equals: delVal } }
129
- });
130
- default:
131
- throw new Error(`Unsupported action: ${action}`);
132
- }
133
- }
134
- async function execute(payload, collection, action, input, config) {
135
- if (input.endsWith(".jsonl")) {
136
- const filePath = import_path.default.resolve(process.cwd(), input);
137
- const rl = import_readline.default.createInterface({ input: import_fs.default.createReadStream(filePath) });
138
- for await (const line of rl) {
139
- if (line.trim()) await processSingle(payload, collection, action, JSON.parse(line), config);
43
+ }).pkgConf("payload-collection-cli").demandCommand(3, "Collection slug, operation, and input are required.").help().parse();
44
+ const { configFile, configJson, configExportName, _: [collection, action, input] } = argv;
45
+ let rawConfig = { mappings: {} };
46
+ if (configJson) {
47
+ try {
48
+ rawConfig = typeof configJson === "string" ? JSON.parse(configJson) : configJson;
49
+ } catch (err) {
50
+ console.error("\u274C Error: Failed to parse inline JSON config:", err);
51
+ process.exit(1);
140
52
  }
141
- return { status: "bulk success" };
142
- }
143
- return await processSingle(payload, collection, action, JSON.parse(input), config);
144
- }
145
-
146
- // src/bin.ts
147
- var jiti = (0, import_jiti.createJiti)(importMetaUrl);
148
- async function run() {
149
- const args = process.argv.slice(2);
150
- let configOptIdx = args.indexOf("-c");
151
- if (configOptIdx === -1) configOptIdx = args.indexOf("--config");
152
- let cliConfig = { mappings: {} };
153
- if (configOptIdx !== -1) {
154
- if (args.length <= configOptIdx + 1) {
155
- console.error("\u274C Error: Missing option after --config parameter.");
53
+ } else if (configFile) {
54
+ const customConfigPath = path.resolve(root, configFile);
55
+ if (!fs.existsSync(customConfigPath)) {
56
+ console.error(`\u274C Error: Config file not found at ${customConfigPath}`);
156
57
  process.exit(1);
157
58
  }
158
- const val = args[configOptIdx + 1];
159
- args.splice(configOptIdx, 2);
160
- if (val.trim().startsWith("{")) {
161
- try {
162
- cliConfig = JSON.parse(val);
163
- } catch (err) {
164
- console.error("\u274C Error: Failed to parse inline JSON config:", err);
165
- process.exit(1);
166
- }
59
+ const imported = await jiti.import(customConfigPath);
60
+ if (imported[configExportName]) {
61
+ rawConfig = imported[configExportName];
167
62
  } else {
168
- const customConfigPath = import_path2.default.resolve(process.cwd(), val);
169
- if (!require("fs").existsSync(customConfigPath)) {
170
- console.error(`\u274C Error: Config file not found at ${customConfigPath}`);
171
- process.exit(1);
172
- }
173
- const imported = await jiti.import(customConfigPath);
174
- cliConfig = imported.cliConfig || imported.default || cliConfig;
63
+ console.error(`\u274C Error: Named export "${configExportName}" not found in ${configFile}`);
64
+ process.exit(1);
175
65
  }
176
66
  }
177
- const [collection, action, input] = args;
178
- const root = process.cwd();
179
- if (!collection || !action || !input) {
180
- console.log("Usage: payload-collection-cli [-c path] <collection> <action> <json|file.jsonl>");
67
+ const validation = CLIConfigSchema.safeParse(rawConfig);
68
+ if (!validation.success) {
69
+ console.error("\u274C Error: Invalid configuration structure:");
70
+ validation.error.issues.forEach((issue) => {
71
+ console.error(` - [${issue.path.join(".")}] ${issue.message}`);
72
+ });
181
73
  process.exit(1);
182
74
  }
75
+ const cliConfig = validation.data;
183
76
  const configPath = [
184
- import_path2.default.resolve(root, "src/payload.config.ts"),
185
- import_path2.default.resolve(root, "payload.config.ts")
186
- ].find((p) => require("fs").existsSync(p));
187
- if (!configPath) throw new Error("payload.config.ts not found.");
77
+ path.resolve(root, "src/payload.config.ts"),
78
+ path.resolve(root, "payload.config.ts")
79
+ ].find((p) => fs.existsSync(p));
80
+ if (!configPath) {
81
+ console.error("\u274C Error: payload.config.ts not found.");
82
+ process.exit(1);
83
+ }
188
84
  const { default: payloadConfig } = await jiti.import(configPath);
189
- const payload = await (0, import_payload.getPayload)({ config: payloadConfig });
85
+ const payload = await getPayload({ config: payloadConfig });
86
+ console.log("\u2705 Connected to Payload");
190
87
  try {
191
88
  const result = await execute(payload, collection, action, input, cliConfig);
192
89
  console.log("\u2728 Operation successful");
@@ -196,4 +93,7 @@ async function run() {
196
93
  }
197
94
  process.exit(0);
198
95
  }
199
- run();
96
+ run().catch((err) => {
97
+ console.error("\u274C Fatal Error:", err.message);
98
+ process.exit(1);
99
+ });
@@ -1,10 +1,3 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
1
  // src/resolver.ts
9
2
  async function resolveRelations(payload, collectionSlug, data, config) {
10
3
  const collection = payload.collections[collectionSlug];
@@ -115,7 +108,6 @@ async function execute(payload, collection, action, input, config) {
115
108
  }
116
109
 
117
110
  export {
118
- __require,
119
111
  resolveRelations,
120
112
  execute
121
113
  };
package/dist/index.cjs ADDED
@@ -0,0 +1,150 @@
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 __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ execute: () => execute,
34
+ resolveRelations: () => resolveRelations
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/resolver.ts
39
+ async function resolveRelations(payload, collectionSlug, data, config) {
40
+ const collection = payload.collections[collectionSlug];
41
+ if (!collection) throw new Error(`Collection "${collectionSlug}" not found.`);
42
+ const resolved = { ...data };
43
+ const mappingConfig = config.mappings[collectionSlug];
44
+ if (mappingConfig?.defaults) {
45
+ for (const [k, v] of Object.entries(mappingConfig.defaults)) {
46
+ if (resolved[k] === void 0 || resolved[k] === null) {
47
+ resolved[k] = v;
48
+ }
49
+ }
50
+ }
51
+ for (const field of collection.config.fields) {
52
+ if (field.type === "relationship" && resolved[field.name]) {
53
+ const relationTo = Array.isArray(field.relationTo) ? field.relationTo[0] : field.relationTo;
54
+ const mapping = config.mappings[relationTo];
55
+ if (!mapping) continue;
56
+ const rawValue = resolved[field.name];
57
+ const isArray = Array.isArray(rawValue);
58
+ const values = isArray ? rawValue : [rawValue];
59
+ const resolvedIds = [];
60
+ for (const val of values) {
61
+ if (typeof val === "string" && val.length < 24) {
62
+ const found = await payload.find({
63
+ collection: relationTo,
64
+ where: { [mapping.lookupField]: { equals: val } },
65
+ limit: 1
66
+ });
67
+ if (found.docs.length > 0) {
68
+ resolvedIds.push(found.docs[0].id);
69
+ } else if (mapping.onNotFound === "create") {
70
+ const created = await payload.create({
71
+ collection: relationTo,
72
+ data: { [mapping.lookupField]: val }
73
+ });
74
+ resolvedIds.push(created.id);
75
+ } else if (mapping.onNotFound === "error") {
76
+ throw new Error(`Relation not found: ${relationTo} (${mapping.lookupField}=${val})`);
77
+ } else {
78
+ resolvedIds.push(val);
79
+ }
80
+ } else {
81
+ resolvedIds.push(val);
82
+ }
83
+ }
84
+ resolved[field.name] = isArray ? resolvedIds : resolvedIds[0];
85
+ }
86
+ }
87
+ return resolved;
88
+ }
89
+
90
+ // src/executor.ts
91
+ var import_fs = __toESM(require("fs"), 1);
92
+ var import_readline = __toESM(require("readline"), 1);
93
+ var import_path = __toESM(require("path"), 1);
94
+ async function processSingle(payload, collection, action, data, config) {
95
+ const mapping = config.mappings[collection];
96
+ const lookupField = mapping?.lookupField || "id";
97
+ const resolved = await resolveRelations(payload, collection, data, config);
98
+ switch (action) {
99
+ case "create":
100
+ return await payload.create({ collection, data: resolved });
101
+ case "upsert":
102
+ if (data[lookupField] === void 0) {
103
+ throw new Error(`[upsert] Missing lookup field '${lookupField}' in data. Cannot perform upsert.`);
104
+ }
105
+ const existingUpsert = await payload.find({
106
+ collection,
107
+ where: { [lookupField]: { equals: data[lookupField] } }
108
+ });
109
+ if (existingUpsert.docs.length > 0) {
110
+ return await payload.update({ collection, id: existingUpsert.docs[0].id, data: resolved });
111
+ }
112
+ return await payload.create({ collection, data: resolved });
113
+ case "update":
114
+ if (data[lookupField] === void 0) {
115
+ throw new Error(`[update] Missing lookup field '${lookupField}' in data. Cannot perform update.`);
116
+ }
117
+ return await payload.update({
118
+ collection,
119
+ where: { [lookupField]: { equals: data[lookupField] } },
120
+ data: resolved
121
+ });
122
+ case "delete":
123
+ const delVal = typeof data === "object" ? data[lookupField] : data;
124
+ if (delVal === void 0) {
125
+ throw new Error(`[delete] Missing lookup field '${lookupField}' in action. Cannot perform delete.`);
126
+ }
127
+ return await payload.delete({
128
+ collection,
129
+ where: { [lookupField]: { equals: delVal } }
130
+ });
131
+ default:
132
+ throw new Error(`Unsupported action: ${action}`);
133
+ }
134
+ }
135
+ async function execute(payload, collection, action, input, config) {
136
+ if (input.endsWith(".jsonl")) {
137
+ const filePath = import_path.default.resolve(process.cwd(), input);
138
+ const rl = import_readline.default.createInterface({ input: import_fs.default.createReadStream(filePath) });
139
+ for await (const line of rl) {
140
+ if (line.trim()) await processSingle(payload, collection, action, JSON.parse(line), config);
141
+ }
142
+ return { status: "bulk success" };
143
+ }
144
+ return await processSingle(payload, collection, action, JSON.parse(input), config);
145
+ }
146
+ // Annotate the CommonJS export names for ESM import in node:
147
+ 0 && (module.exports = {
148
+ execute,
149
+ resolveRelations
150
+ });
@@ -3,11 +3,11 @@ import { Payload } from 'payload';
3
3
 
4
4
  interface MappingConfig {
5
5
  lookupField: string;
6
- onNotFound?: 'error' | 'ignore' | 'create';
6
+ onNotFound: 'error' | 'ignore' | 'create';
7
7
  defaults?: Record<string, any>;
8
8
  }
9
9
  interface CLIConfig {
10
- mappings: Record<string, MappingConfig>;
10
+ mappings: Record<string, MappingConfig | undefined>;
11
11
  }
12
12
  type Action = 'create' | 'update' | 'delete' | 'find' | 'upsert';
13
13
  interface CommandArgs {
package/dist/index.d.ts CHANGED
@@ -3,11 +3,11 @@ import { Payload } from 'payload';
3
3
 
4
4
  interface MappingConfig {
5
5
  lookupField: string;
6
- onNotFound?: 'error' | 'ignore' | 'create';
6
+ onNotFound: 'error' | 'ignore' | 'create';
7
7
  defaults?: Record<string, any>;
8
8
  }
9
9
  interface CLIConfig {
10
- mappings: Record<string, MappingConfig>;
10
+ mappings: Record<string, MappingConfig | undefined>;
11
11
  }
12
12
  type Action = 'create' | 'update' | 'delete' | 'find' | 'upsert';
13
13
  interface CommandArgs {
package/dist/index.js CHANGED
@@ -1,150 +1,8 @@
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 __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- execute: () => execute,
34
- resolveRelations: () => resolveRelations
35
- });
36
- module.exports = __toCommonJS(index_exports);
37
-
38
- // src/resolver.ts
39
- async function resolveRelations(payload, collectionSlug, data, config) {
40
- const collection = payload.collections[collectionSlug];
41
- if (!collection) throw new Error(`Collection "${collectionSlug}" not found.`);
42
- const resolved = { ...data };
43
- const mappingConfig = config.mappings[collectionSlug];
44
- if (mappingConfig?.defaults) {
45
- for (const [k, v] of Object.entries(mappingConfig.defaults)) {
46
- if (resolved[k] === void 0 || resolved[k] === null) {
47
- resolved[k] = v;
48
- }
49
- }
50
- }
51
- for (const field of collection.config.fields) {
52
- if (field.type === "relationship" && resolved[field.name]) {
53
- const relationTo = Array.isArray(field.relationTo) ? field.relationTo[0] : field.relationTo;
54
- const mapping = config.mappings[relationTo];
55
- if (!mapping) continue;
56
- const rawValue = resolved[field.name];
57
- const isArray = Array.isArray(rawValue);
58
- const values = isArray ? rawValue : [rawValue];
59
- const resolvedIds = [];
60
- for (const val of values) {
61
- if (typeof val === "string" && val.length < 24) {
62
- const found = await payload.find({
63
- collection: relationTo,
64
- where: { [mapping.lookupField]: { equals: val } },
65
- limit: 1
66
- });
67
- if (found.docs.length > 0) {
68
- resolvedIds.push(found.docs[0].id);
69
- } else if (mapping.onNotFound === "create") {
70
- const created = await payload.create({
71
- collection: relationTo,
72
- data: { [mapping.lookupField]: val }
73
- });
74
- resolvedIds.push(created.id);
75
- } else if (mapping.onNotFound === "error") {
76
- throw new Error(`Relation not found: ${relationTo} (${mapping.lookupField}=${val})`);
77
- } else {
78
- resolvedIds.push(val);
79
- }
80
- } else {
81
- resolvedIds.push(val);
82
- }
83
- }
84
- resolved[field.name] = isArray ? resolvedIds : resolvedIds[0];
85
- }
86
- }
87
- return resolved;
88
- }
89
-
90
- // src/executor.ts
91
- var import_fs = __toESM(require("fs"));
92
- var import_readline = __toESM(require("readline"));
93
- var import_path = __toESM(require("path"));
94
- async function processSingle(payload, collection, action, data, config) {
95
- const mapping = config.mappings[collection];
96
- const lookupField = mapping?.lookupField || "id";
97
- const resolved = await resolveRelations(payload, collection, data, config);
98
- switch (action) {
99
- case "create":
100
- return await payload.create({ collection, data: resolved });
101
- case "upsert":
102
- if (data[lookupField] === void 0) {
103
- throw new Error(`[upsert] Missing lookup field '${lookupField}' in data. Cannot perform upsert.`);
104
- }
105
- const existingUpsert = await payload.find({
106
- collection,
107
- where: { [lookupField]: { equals: data[lookupField] } }
108
- });
109
- if (existingUpsert.docs.length > 0) {
110
- return await payload.update({ collection, id: existingUpsert.docs[0].id, data: resolved });
111
- }
112
- return await payload.create({ collection, data: resolved });
113
- case "update":
114
- if (data[lookupField] === void 0) {
115
- throw new Error(`[update] Missing lookup field '${lookupField}' in data. Cannot perform update.`);
116
- }
117
- return await payload.update({
118
- collection,
119
- where: { [lookupField]: { equals: data[lookupField] } },
120
- data: resolved
121
- });
122
- case "delete":
123
- const delVal = typeof data === "object" ? data[lookupField] : data;
124
- if (delVal === void 0) {
125
- throw new Error(`[delete] Missing lookup field '${lookupField}' in action. Cannot perform delete.`);
126
- }
127
- return await payload.delete({
128
- collection,
129
- where: { [lookupField]: { equals: delVal } }
130
- });
131
- default:
132
- throw new Error(`Unsupported action: ${action}`);
133
- }
134
- }
135
- async function execute(payload, collection, action, input, config) {
136
- if (input.endsWith(".jsonl")) {
137
- const filePath = import_path.default.resolve(process.cwd(), input);
138
- const rl = import_readline.default.createInterface({ input: import_fs.default.createReadStream(filePath) });
139
- for await (const line of rl) {
140
- if (line.trim()) await processSingle(payload, collection, action, JSON.parse(line), config);
141
- }
142
- return { status: "bulk success" };
143
- }
144
- return await processSingle(payload, collection, action, JSON.parse(input), config);
145
- }
146
- // Annotate the CommonJS export names for ESM import in node:
147
- 0 && (module.exports = {
1
+ import {
148
2
  execute,
149
3
  resolveRelations
150
- });
4
+ } from "./chunk-D5RS7XAO.js";
5
+ export {
6
+ execute,
7
+ resolveRelations
8
+ };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@payload-cc/payload-collection-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
+ "type": "module",
4
5
  "description": "Functional CLI for Payload 3.0 collection management",
5
6
  "repository": {
6
7
  "type": "git",
@@ -23,7 +24,10 @@
23
24
  "build": "tsup src/index.ts src/bin.ts --format cjs,esm --dts --clean --shims && chmod +x dist/bin.js",
24
25
  "release": "pnpm build && pnpm publish --access public",
25
26
  "test": "vitest run tests/*",
26
- "test:e2e": "vitest run e2e-tests/scenarios/ --no-file-parallelism"
27
+ "test:e2e": "vitest run e2e-tests/scenarios/ --no-file-parallelism",
28
+ "docs:dev": "vitepress dev docs",
29
+ "docs:build": "vitepress build docs",
30
+ "docs:preview": "vitepress preview docs"
27
31
  },
28
32
  "keywords": [
29
33
  "payload",
@@ -37,13 +41,17 @@
37
41
  "payload": "^3.0.0"
38
42
  },
39
43
  "dependencies": {
40
- "jiti": "^2.6.1"
44
+ "jiti": "^2.6.1",
45
+ "yargs": "^18.0.0",
46
+ "zod": "^4.3.6"
41
47
  },
42
48
  "devDependencies": {
43
49
  "@types/node": "^25.5.0",
50
+ "@types/yargs": "^17.0.35",
44
51
  "payload": "^3.79.1",
45
52
  "tsup": "^8.5.1",
46
53
  "typescript": "^5.9.3",
54
+ "vitepress": "^1.6.4",
47
55
  "vitest": "^4.1.0"
48
56
  }
49
57
  }
package/dist/bin.mjs DELETED
@@ -1,63 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- __require,
4
- execute
5
- } from "./chunk-RIXWYITK.mjs";
6
-
7
- // src/bin.ts
8
- import { getPayload } from "payload";
9
- import { createJiti } from "jiti";
10
- import path from "path";
11
- var jiti = createJiti(import.meta.url);
12
- async function run() {
13
- const args = process.argv.slice(2);
14
- let configOptIdx = args.indexOf("-c");
15
- if (configOptIdx === -1) configOptIdx = args.indexOf("--config");
16
- let cliConfig = { mappings: {} };
17
- if (configOptIdx !== -1) {
18
- if (args.length <= configOptIdx + 1) {
19
- console.error("\u274C Error: Missing option after --config parameter.");
20
- process.exit(1);
21
- }
22
- const val = args[configOptIdx + 1];
23
- args.splice(configOptIdx, 2);
24
- if (val.trim().startsWith("{")) {
25
- try {
26
- cliConfig = JSON.parse(val);
27
- } catch (err) {
28
- console.error("\u274C Error: Failed to parse inline JSON config:", err);
29
- process.exit(1);
30
- }
31
- } else {
32
- const customConfigPath = path.resolve(process.cwd(), val);
33
- if (!__require("fs").existsSync(customConfigPath)) {
34
- console.error(`\u274C Error: Config file not found at ${customConfigPath}`);
35
- process.exit(1);
36
- }
37
- const imported = await jiti.import(customConfigPath);
38
- cliConfig = imported.cliConfig || imported.default || cliConfig;
39
- }
40
- }
41
- const [collection, action, input] = args;
42
- const root = process.cwd();
43
- if (!collection || !action || !input) {
44
- console.log("Usage: payload-collection-cli [-c path] <collection> <action> <json|file.jsonl>");
45
- process.exit(1);
46
- }
47
- const configPath = [
48
- path.resolve(root, "src/payload.config.ts"),
49
- path.resolve(root, "payload.config.ts")
50
- ].find((p) => __require("fs").existsSync(p));
51
- if (!configPath) throw new Error("payload.config.ts not found.");
52
- const { default: payloadConfig } = await jiti.import(configPath);
53
- const payload = await getPayload({ config: payloadConfig });
54
- try {
55
- const result = await execute(payload, collection, action, input, cliConfig);
56
- console.log("\u2728 Operation successful");
57
- } catch (err) {
58
- console.error("\u274C Error:", err.message);
59
- process.exit(1);
60
- }
61
- process.exit(0);
62
- }
63
- run();
package/dist/index.mjs DELETED
@@ -1,8 +0,0 @@
1
- import {
2
- execute,
3
- resolveRelations
4
- } from "./chunk-RIXWYITK.mjs";
5
- export {
6
- execute,
7
- resolveRelations
8
- };
File without changes