@payload-cc/payload-collection-cli 1.0.9 → 1.1.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 +20 -3
- package/dist/bin.js +44 -12
- package/dist/bin.mjs +31 -9
- package/dist/{chunk-3HKWP2RE.mjs → chunk-I7HHN7EL.mjs} +14 -4
- package/dist/index.js +14 -4
- package/dist/index.mjs +1 -1
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -13,14 +13,23 @@ pnpm add @payload-cc/payload-collection-cli
|
|
|
13
13
|
|
|
14
14
|
## Quick Start
|
|
15
15
|
|
|
16
|
-
You can immediately start using the commands without any configuration!
|
|
16
|
+
You can immediately start using the commands without any configuration for basic insertions/updates!
|
|
17
17
|
|
|
18
|
+
**Command Syntax**:
|
|
19
|
+
```bash
|
|
20
|
+
npx @payload-cc/payload-collection-cli [-c config-file] <collection-slug> <operation> <file or string>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Examples**:
|
|
18
24
|
```bash
|
|
19
25
|
# Bulk create/upsert from jsonlines
|
|
20
26
|
npx @payload-cc/payload-collection-cli posts upsert data.jsonl
|
|
21
27
|
|
|
22
28
|
# Simple JSON update
|
|
23
29
|
npx @payload-cc/payload-collection-cli users update '{"email": "user@example.com", "name": "New Name"}'
|
|
30
|
+
|
|
31
|
+
# With explicit mapping configuration
|
|
32
|
+
npx @payload-cc/payload-collection-cli -c ./my-map.config.ts users upsert data.jsonl
|
|
24
33
|
```
|
|
25
34
|
|
|
26
35
|
## Available Actions
|
|
@@ -29,10 +38,18 @@ npx @payload-cc/payload-collection-cli users update '{"email": "user@example.com
|
|
|
29
38
|
- `delete`: Delete records.
|
|
30
39
|
- `upsert`: Update if existing, create if not.
|
|
31
40
|
|
|
41
|
+
## 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
|
+
|
|
32
44
|
## Configuration (Optional)
|
|
33
45
|
|
|
34
46
|
### Relation Mappings (`payload-collection-cli.config.ts`)
|
|
35
|
-
|
|
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:
|
|
36
53
|
|
|
37
54
|
```typescript
|
|
38
55
|
export const cliConfig = {
|
|
@@ -48,7 +65,7 @@ export const cliConfig = {
|
|
|
48
65
|
}
|
|
49
66
|
```
|
|
50
67
|
|
|
51
|
-
Now, when you supply an `author: "user@example.com"` property to a `posts` collection insertion, the CLI will look up the
|
|
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!
|
|
52
69
|
|
|
53
70
|
## Development & CI/CD
|
|
54
71
|
|
package/dist/bin.js
CHANGED
|
@@ -84,24 +84,34 @@ async function processSingle(payload, collection, action, data, config) {
|
|
|
84
84
|
case "create":
|
|
85
85
|
return await payload.create({ collection, data: resolved });
|
|
86
86
|
case "upsert":
|
|
87
|
-
|
|
87
|
+
if (data[lookupField] === void 0) {
|
|
88
|
+
throw new Error(`[upsert] Missing lookup field '${lookupField}' in data. Cannot perform upsert.`);
|
|
89
|
+
}
|
|
90
|
+
const existingUpsert = await payload.find({
|
|
88
91
|
collection,
|
|
89
92
|
where: { [lookupField]: { equals: data[lookupField] } }
|
|
90
93
|
});
|
|
91
|
-
if (
|
|
92
|
-
return await payload.update({ collection, id:
|
|
94
|
+
if (existingUpsert.docs.length > 0) {
|
|
95
|
+
return await payload.update({ collection, id: existingUpsert.docs[0].id, data: resolved });
|
|
93
96
|
}
|
|
94
97
|
return await payload.create({ collection, data: resolved });
|
|
95
98
|
case "update":
|
|
99
|
+
if (data[lookupField] === void 0) {
|
|
100
|
+
throw new Error(`[update] Missing lookup field '${lookupField}' in data. Cannot perform update.`);
|
|
101
|
+
}
|
|
96
102
|
return await payload.update({
|
|
97
103
|
collection,
|
|
98
104
|
where: { [lookupField]: { equals: data[lookupField] } },
|
|
99
105
|
data: resolved
|
|
100
106
|
});
|
|
101
107
|
case "delete":
|
|
108
|
+
const delVal = typeof data === "object" ? data[lookupField] : data;
|
|
109
|
+
if (delVal === void 0) {
|
|
110
|
+
throw new Error(`[delete] Missing lookup field '${lookupField}' in action. Cannot perform delete.`);
|
|
111
|
+
}
|
|
102
112
|
return await payload.delete({
|
|
103
113
|
collection,
|
|
104
|
-
where: { [lookupField]: { equals:
|
|
114
|
+
where: { [lookupField]: { equals: delVal } }
|
|
105
115
|
});
|
|
106
116
|
default:
|
|
107
117
|
throw new Error(`Unsupported action: ${action}`);
|
|
@@ -122,10 +132,38 @@ async function execute(payload, collection, action, input, config) {
|
|
|
122
132
|
// src/bin.ts
|
|
123
133
|
var jiti = (0, import_jiti.createJiti)(importMetaUrl);
|
|
124
134
|
async function run() {
|
|
125
|
-
const
|
|
135
|
+
const args = process.argv.slice(2);
|
|
136
|
+
let configOptIdx = args.indexOf("-c");
|
|
137
|
+
if (configOptIdx === -1) configOptIdx = args.indexOf("--config");
|
|
138
|
+
let cliConfig = { mappings: {} };
|
|
139
|
+
if (configOptIdx !== -1) {
|
|
140
|
+
if (args.length <= configOptIdx + 1) {
|
|
141
|
+
console.error("\u274C Error: Missing option after --config parameter.");
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
const val = args[configOptIdx + 1];
|
|
145
|
+
args.splice(configOptIdx, 2);
|
|
146
|
+
if (val.trim().startsWith("{")) {
|
|
147
|
+
try {
|
|
148
|
+
cliConfig = JSON.parse(val);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error("\u274C Error: Failed to parse inline JSON config:", err);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
const customConfigPath = import_path2.default.resolve(process.cwd(), val);
|
|
155
|
+
if (!require("fs").existsSync(customConfigPath)) {
|
|
156
|
+
console.error(`\u274C Error: Config file not found at ${customConfigPath}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
const imported = await jiti.import(customConfigPath);
|
|
160
|
+
cliConfig = imported.cliConfig || imported.default || cliConfig;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const [collection, action, input] = args;
|
|
126
164
|
const root = process.cwd();
|
|
127
165
|
if (!collection || !action || !input) {
|
|
128
|
-
console.log("Usage: payload-collection-cli <collection> <action> <json|file.jsonl>");
|
|
166
|
+
console.log("Usage: payload-collection-cli [-c path] <collection> <action> <json|file.jsonl>");
|
|
129
167
|
process.exit(1);
|
|
130
168
|
}
|
|
131
169
|
const configPath = [
|
|
@@ -134,12 +172,6 @@ async function run() {
|
|
|
134
172
|
].find((p) => require("fs").existsSync(p));
|
|
135
173
|
if (!configPath) throw new Error("payload.config.ts not found.");
|
|
136
174
|
const { default: payloadConfig } = await jiti.import(configPath);
|
|
137
|
-
let cliConfig = { mappings: {} };
|
|
138
|
-
const cliCfgPath = import_path2.default.resolve(root, "payload-collection-cli.config.ts");
|
|
139
|
-
if (require("fs").existsSync(cliCfgPath)) {
|
|
140
|
-
const imported = await jiti.import(cliCfgPath);
|
|
141
|
-
cliConfig = imported.cliConfig || imported.default || cliConfig;
|
|
142
|
-
}
|
|
143
175
|
const payload = await (0, import_payload.getPayload)({ config: payloadConfig });
|
|
144
176
|
try {
|
|
145
177
|
const result = await execute(payload, collection, action, input, cliConfig);
|
package/dist/bin.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
__require,
|
|
4
4
|
execute
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-I7HHN7EL.mjs";
|
|
6
6
|
|
|
7
7
|
// src/bin.ts
|
|
8
8
|
import { getPayload } from "payload";
|
|
@@ -10,10 +10,38 @@ import { createJiti } from "jiti";
|
|
|
10
10
|
import path from "path";
|
|
11
11
|
var jiti = createJiti(import.meta.url);
|
|
12
12
|
async function run() {
|
|
13
|
-
const
|
|
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;
|
|
14
42
|
const root = process.cwd();
|
|
15
43
|
if (!collection || !action || !input) {
|
|
16
|
-
console.log("Usage: payload-collection-cli <collection> <action> <json|file.jsonl>");
|
|
44
|
+
console.log("Usage: payload-collection-cli [-c path] <collection> <action> <json|file.jsonl>");
|
|
17
45
|
process.exit(1);
|
|
18
46
|
}
|
|
19
47
|
const configPath = [
|
|
@@ -22,12 +50,6 @@ async function run() {
|
|
|
22
50
|
].find((p) => __require("fs").existsSync(p));
|
|
23
51
|
if (!configPath) throw new Error("payload.config.ts not found.");
|
|
24
52
|
const { default: payloadConfig } = await jiti.import(configPath);
|
|
25
|
-
let cliConfig = { mappings: {} };
|
|
26
|
-
const cliCfgPath = path.resolve(root, "payload-collection-cli.config.ts");
|
|
27
|
-
if (__require("fs").existsSync(cliCfgPath)) {
|
|
28
|
-
const imported = await jiti.import(cliCfgPath);
|
|
29
|
-
cliConfig = imported.cliConfig || imported.default || cliConfig;
|
|
30
|
-
}
|
|
31
53
|
const payload = await getPayload({ config: payloadConfig });
|
|
32
54
|
try {
|
|
33
55
|
const result = await execute(payload, collection, action, input, cliConfig);
|
|
@@ -55,24 +55,34 @@ async function processSingle(payload, collection, action, data, config) {
|
|
|
55
55
|
case "create":
|
|
56
56
|
return await payload.create({ collection, data: resolved });
|
|
57
57
|
case "upsert":
|
|
58
|
-
|
|
58
|
+
if (data[lookupField] === void 0) {
|
|
59
|
+
throw new Error(`[upsert] Missing lookup field '${lookupField}' in data. Cannot perform upsert.`);
|
|
60
|
+
}
|
|
61
|
+
const existingUpsert = await payload.find({
|
|
59
62
|
collection,
|
|
60
63
|
where: { [lookupField]: { equals: data[lookupField] } }
|
|
61
64
|
});
|
|
62
|
-
if (
|
|
63
|
-
return await payload.update({ collection, id:
|
|
65
|
+
if (existingUpsert.docs.length > 0) {
|
|
66
|
+
return await payload.update({ collection, id: existingUpsert.docs[0].id, data: resolved });
|
|
64
67
|
}
|
|
65
68
|
return await payload.create({ collection, data: resolved });
|
|
66
69
|
case "update":
|
|
70
|
+
if (data[lookupField] === void 0) {
|
|
71
|
+
throw new Error(`[update] Missing lookup field '${lookupField}' in data. Cannot perform update.`);
|
|
72
|
+
}
|
|
67
73
|
return await payload.update({
|
|
68
74
|
collection,
|
|
69
75
|
where: { [lookupField]: { equals: data[lookupField] } },
|
|
70
76
|
data: resolved
|
|
71
77
|
});
|
|
72
78
|
case "delete":
|
|
79
|
+
const delVal = typeof data === "object" ? data[lookupField] : data;
|
|
80
|
+
if (delVal === void 0) {
|
|
81
|
+
throw new Error(`[delete] Missing lookup field '${lookupField}' in action. Cannot perform delete.`);
|
|
82
|
+
}
|
|
73
83
|
return await payload.delete({
|
|
74
84
|
collection,
|
|
75
|
-
where: { [lookupField]: { equals:
|
|
85
|
+
where: { [lookupField]: { equals: delVal } }
|
|
76
86
|
});
|
|
77
87
|
default:
|
|
78
88
|
throw new Error(`Unsupported action: ${action}`);
|
package/dist/index.js
CHANGED
|
@@ -85,24 +85,34 @@ async function processSingle(payload, collection, action, data, config) {
|
|
|
85
85
|
case "create":
|
|
86
86
|
return await payload.create({ collection, data: resolved });
|
|
87
87
|
case "upsert":
|
|
88
|
-
|
|
88
|
+
if (data[lookupField] === void 0) {
|
|
89
|
+
throw new Error(`[upsert] Missing lookup field '${lookupField}' in data. Cannot perform upsert.`);
|
|
90
|
+
}
|
|
91
|
+
const existingUpsert = await payload.find({
|
|
89
92
|
collection,
|
|
90
93
|
where: { [lookupField]: { equals: data[lookupField] } }
|
|
91
94
|
});
|
|
92
|
-
if (
|
|
93
|
-
return await payload.update({ collection, id:
|
|
95
|
+
if (existingUpsert.docs.length > 0) {
|
|
96
|
+
return await payload.update({ collection, id: existingUpsert.docs[0].id, data: resolved });
|
|
94
97
|
}
|
|
95
98
|
return await payload.create({ collection, data: resolved });
|
|
96
99
|
case "update":
|
|
100
|
+
if (data[lookupField] === void 0) {
|
|
101
|
+
throw new Error(`[update] Missing lookup field '${lookupField}' in data. Cannot perform update.`);
|
|
102
|
+
}
|
|
97
103
|
return await payload.update({
|
|
98
104
|
collection,
|
|
99
105
|
where: { [lookupField]: { equals: data[lookupField] } },
|
|
100
106
|
data: resolved
|
|
101
107
|
});
|
|
102
108
|
case "delete":
|
|
109
|
+
const delVal = typeof data === "object" ? data[lookupField] : data;
|
|
110
|
+
if (delVal === void 0) {
|
|
111
|
+
throw new Error(`[delete] Missing lookup field '${lookupField}' in action. Cannot perform delete.`);
|
|
112
|
+
}
|
|
103
113
|
return await payload.delete({
|
|
104
114
|
collection,
|
|
105
|
-
where: { [lookupField]: { equals:
|
|
115
|
+
where: { [lookupField]: { equals: delVal } }
|
|
106
116
|
});
|
|
107
117
|
default:
|
|
108
118
|
throw new Error(`Unsupported action: ${action}`);
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payload-cc/payload-collection-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Functional CLI for Payload 3.0 collection management",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsup src/index.ts src/bin.ts --format cjs,esm --dts --clean --shims && chmod +x dist/bin.js",
|
|
24
24
|
"release": "pnpm build && pnpm publish --access public",
|
|
25
|
-
"test": "vitest run"
|
|
25
|
+
"test": "vitest run tests/*",
|
|
26
|
+
"test:e2e": "vitest run e2e-tests/scenarios/ --no-file-parallelism"
|
|
26
27
|
},
|
|
27
28
|
"keywords": [
|
|
28
29
|
"payload",
|
|
@@ -40,9 +41,7 @@
|
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@types/node": "^25.5.0",
|
|
43
|
-
"next": "^16.2.0",
|
|
44
44
|
"payload": "^3.79.1",
|
|
45
|
-
"react": "^19.2.4",
|
|
46
45
|
"tsup": "^8.5.1",
|
|
47
46
|
"typescript": "^5.9.3",
|
|
48
47
|
"vitest": "^4.1.0"
|