cmx-sdk 0.1.17 → 0.1.18
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/dist/cli.js +1232 -456
- package/dist/index.d.ts +292 -16
- package/dist/index.js +92 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,262 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
#!/usr/bin/env node
|
|
3
2
|
|
|
4
3
|
// src/cli.ts
|
|
5
4
|
import { Command } from "commander";
|
|
6
5
|
import { config } from "dotenv";
|
|
7
6
|
|
|
8
|
-
// src/
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// src/generated/sdk/endpoints/sdk.ts
|
|
18
|
-
var getGetSchemaUrl = () => {
|
|
19
|
-
return `/api/v1/sdk/schema`;
|
|
20
|
-
};
|
|
21
|
-
var getSchema = async (options) => {
|
|
22
|
-
const res = await fetch(
|
|
23
|
-
getGetSchemaUrl(),
|
|
24
|
-
{
|
|
25
|
-
...options,
|
|
26
|
-
method: "GET"
|
|
27
|
-
}
|
|
28
|
-
);
|
|
29
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
30
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
31
|
-
return { data, status: res.status, headers: res.headers };
|
|
32
|
-
};
|
|
33
|
-
var getGetManageCollectionsUrl = () => {
|
|
34
|
-
return `/api/v1/sdk/manage/collections`;
|
|
35
|
-
};
|
|
36
|
-
var getManageCollections = async (options) => {
|
|
37
|
-
const res = await fetch(
|
|
38
|
-
getGetManageCollectionsUrl(),
|
|
39
|
-
{
|
|
40
|
-
...options,
|
|
41
|
-
method: "GET"
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
45
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
46
|
-
return { data, status: res.status, headers: res.headers };
|
|
47
|
-
};
|
|
48
|
-
var getPostManageCollectionsUrl = () => {
|
|
49
|
-
return `/api/v1/sdk/manage/collections`;
|
|
50
|
-
};
|
|
51
|
-
var postManageCollections = async (createCollectionRequest, options) => {
|
|
52
|
-
const res = await fetch(
|
|
53
|
-
getPostManageCollectionsUrl(),
|
|
54
|
-
{
|
|
55
|
-
...options,
|
|
56
|
-
method: "POST",
|
|
57
|
-
headers: { "Content-Type": "application/json", ...options?.headers },
|
|
58
|
-
body: JSON.stringify(
|
|
59
|
-
createCollectionRequest
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
64
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
65
|
-
return { data, status: res.status, headers: res.headers };
|
|
66
|
-
};
|
|
67
|
-
var getPutManageCollectionsSlugUrl = (slug) => {
|
|
68
|
-
return `/api/v1/sdk/manage/collections/${slug}`;
|
|
69
|
-
};
|
|
70
|
-
var putManageCollectionsSlug = async (slug, updateCollectionRequest, options) => {
|
|
71
|
-
const res = await fetch(
|
|
72
|
-
getPutManageCollectionsSlugUrl(slug),
|
|
73
|
-
{
|
|
74
|
-
...options,
|
|
75
|
-
method: "PUT",
|
|
76
|
-
headers: { "Content-Type": "application/json", ...options?.headers },
|
|
77
|
-
body: JSON.stringify(
|
|
78
|
-
updateCollectionRequest
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
);
|
|
82
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
83
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
84
|
-
return { data, status: res.status, headers: res.headers };
|
|
85
|
-
};
|
|
86
|
-
var getDeleteManageCollectionsSlugUrl = (slug) => {
|
|
87
|
-
return `/api/v1/sdk/manage/collections/${slug}`;
|
|
88
|
-
};
|
|
89
|
-
var deleteManageCollectionsSlug = async (slug, options) => {
|
|
90
|
-
const res = await fetch(
|
|
91
|
-
getDeleteManageCollectionsSlugUrl(slug),
|
|
92
|
-
{
|
|
93
|
-
...options,
|
|
94
|
-
method: "DELETE"
|
|
95
|
-
}
|
|
96
|
-
);
|
|
97
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
98
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
99
|
-
return { data, status: res.status, headers: res.headers };
|
|
100
|
-
};
|
|
101
|
-
var getGetManageDataTypesUrl = () => {
|
|
102
|
-
return `/api/v1/sdk/manage/data-types`;
|
|
103
|
-
};
|
|
104
|
-
var getManageDataTypes = async (options) => {
|
|
105
|
-
const res = await fetch(
|
|
106
|
-
getGetManageDataTypesUrl(),
|
|
107
|
-
{
|
|
108
|
-
...options,
|
|
109
|
-
method: "GET"
|
|
110
|
-
}
|
|
111
|
-
);
|
|
112
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
113
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
114
|
-
return { data, status: res.status, headers: res.headers };
|
|
115
|
-
};
|
|
116
|
-
var getPostManageDataTypesUrl = () => {
|
|
117
|
-
return `/api/v1/sdk/manage/data-types`;
|
|
118
|
-
};
|
|
119
|
-
var postManageDataTypes = async (createDataTypeRequest, options) => {
|
|
120
|
-
const res = await fetch(
|
|
121
|
-
getPostManageDataTypesUrl(),
|
|
122
|
-
{
|
|
123
|
-
...options,
|
|
124
|
-
method: "POST",
|
|
125
|
-
headers: { "Content-Type": "application/json", ...options?.headers },
|
|
126
|
-
body: JSON.stringify(
|
|
127
|
-
createDataTypeRequest
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
);
|
|
131
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
132
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
133
|
-
return { data, status: res.status, headers: res.headers };
|
|
134
|
-
};
|
|
135
|
-
var getPutManageDataTypesSlugUrl = (slug) => {
|
|
136
|
-
return `/api/v1/sdk/manage/data-types/${slug}`;
|
|
137
|
-
};
|
|
138
|
-
var putManageDataTypesSlug = async (slug, updateDataTypeRequest, options) => {
|
|
139
|
-
const res = await fetch(
|
|
140
|
-
getPutManageDataTypesSlugUrl(slug),
|
|
141
|
-
{
|
|
142
|
-
...options,
|
|
143
|
-
method: "PUT",
|
|
144
|
-
headers: { "Content-Type": "application/json", ...options?.headers },
|
|
145
|
-
body: JSON.stringify(
|
|
146
|
-
updateDataTypeRequest
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
);
|
|
150
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
151
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
152
|
-
return { data, status: res.status, headers: res.headers };
|
|
153
|
-
};
|
|
154
|
-
var getDeleteManageDataTypesSlugUrl = (slug) => {
|
|
155
|
-
return `/api/v1/sdk/manage/data-types/${slug}`;
|
|
156
|
-
};
|
|
157
|
-
var deleteManageDataTypesSlug = async (slug, options) => {
|
|
158
|
-
const res = await fetch(
|
|
159
|
-
getDeleteManageDataTypesSlugUrl(slug),
|
|
160
|
-
{
|
|
161
|
-
...options,
|
|
162
|
-
method: "DELETE"
|
|
163
|
-
}
|
|
164
|
-
);
|
|
165
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
166
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
167
|
-
return { data, status: res.status, headers: res.headers };
|
|
168
|
-
};
|
|
169
|
-
var getGetManageComponentsUrl = () => {
|
|
170
|
-
return `/api/v1/sdk/manage/components`;
|
|
171
|
-
};
|
|
172
|
-
var getManageComponents = async (options) => {
|
|
173
|
-
const res = await fetch(
|
|
174
|
-
getGetManageComponentsUrl(),
|
|
175
|
-
{
|
|
176
|
-
...options,
|
|
177
|
-
method: "GET"
|
|
178
|
-
}
|
|
179
|
-
);
|
|
180
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
181
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
182
|
-
return { data, status: res.status, headers: res.headers };
|
|
183
|
-
};
|
|
184
|
-
var getGetManageFormDefinitionsUrl = () => {
|
|
185
|
-
return `/api/v1/sdk/manage/form-definitions`;
|
|
186
|
-
};
|
|
187
|
-
var getManageFormDefinitions = async (options) => {
|
|
188
|
-
const res = await fetch(
|
|
189
|
-
getGetManageFormDefinitionsUrl(),
|
|
190
|
-
{
|
|
191
|
-
...options,
|
|
192
|
-
method: "GET"
|
|
193
|
-
}
|
|
194
|
-
);
|
|
195
|
-
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
|
|
196
|
-
const data = body ? JSON.parse(body, dateReviver) : {};
|
|
197
|
-
return { data, status: res.status, headers: res.headers };
|
|
198
|
-
};
|
|
199
|
-
var getPostManageFormDefinitionsUrl = () => {
|
|
200
|
-
return `/api/v1/sdk/manage/form-definitions`;
|
|
201
|
-
};
|
|
202
|
-
var postManageFormDefinitions = async (createFormDefinitionRequest, options) => {
|
|
203
|
-
const res = await fetch(
|
|
204
|
-
getPostManageFormDefinitionsUrl(),
|
|
205
|
-
{
|
|
206
|
-
...options,
|
|
207
|
-
method: "POST",
|
|
208
|
-
headers: { "Content-Type": "application/json", ...options?.headers },
|
|
209
|
-
body: JSON.stringify(
|
|
210
|
-
createFormDefinitionRequest
|
|
211
|
-
)
|
|
7
|
+
// src/commands/utils.ts
|
|
8
|
+
import { readFileSync } from "fs";
|
|
9
|
+
function readJsonInput(options) {
|
|
10
|
+
if (options.json) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(options.json);
|
|
13
|
+
} catch {
|
|
14
|
+
console.error("Error: Invalid JSON string provided to --json");
|
|
15
|
+
process.exit(1);
|
|
212
16
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const res = await fetch(
|
|
223
|
-
getPutManageFormDefinitionsSlugUrl(slug),
|
|
224
|
-
{
|
|
225
|
-
...options,
|
|
226
|
-
method: "PUT",
|
|
227
|
-
headers: { "Content-Type": "application/json", ...options?.headers },
|
|
228
|
-
body: JSON.stringify(
|
|
229
|
-
updateFormDefinitionRequest
|
|
230
|
-
)
|
|
17
|
+
}
|
|
18
|
+
if (options.file) {
|
|
19
|
+
try {
|
|
20
|
+
const content = readFileSync(options.file, "utf-8");
|
|
21
|
+
return JSON.parse(content);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error(`Error: Failed to read JSON file: ${options.file}`);
|
|
24
|
+
console.error(err instanceof Error ? err.message : err);
|
|
25
|
+
process.exit(1);
|
|
231
26
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
27
|
+
}
|
|
28
|
+
console.error("Error: --json or --file is required");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
async function manageApiFetch(apiUrl, apiKey, method, path, body) {
|
|
32
|
+
const url = `${apiUrl}/api/v1/sdk/manage${path}`;
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
method,
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
Authorization: `Bearer ${apiKey}`
|
|
38
|
+
},
|
|
39
|
+
...body ? { body: JSON.stringify(body) } : {}
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const errorBody = await response.text();
|
|
43
|
+
let errorMsg;
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(errorBody);
|
|
46
|
+
errorMsg = parsed.error || errorBody;
|
|
47
|
+
} catch {
|
|
48
|
+
errorMsg = errorBody;
|
|
246
49
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
};
|
|
50
|
+
throw new Error(`API error (${response.status}): ${errorMsg}`);
|
|
51
|
+
}
|
|
52
|
+
return response.json();
|
|
53
|
+
}
|
|
252
54
|
|
|
253
55
|
// src/commands/list-collections.ts
|
|
254
56
|
async function listCollections() {
|
|
57
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
58
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
59
|
+
if (!apiUrl || !apiKey) {
|
|
60
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
255
63
|
try {
|
|
256
64
|
console.log("Fetching collections...");
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
if (collections.length === 0) {
|
|
65
|
+
const collections = await manageApiFetch(apiUrl, apiKey, "GET", "/collections");
|
|
66
|
+
if (!Array.isArray(collections) || collections.length === 0) {
|
|
260
67
|
console.log("No collections found.");
|
|
261
68
|
return;
|
|
262
69
|
}
|
|
@@ -279,11 +86,16 @@ Found ${collections.length} collection(s):
|
|
|
279
86
|
|
|
280
87
|
// src/commands/list-data-types.ts
|
|
281
88
|
async function listDataTypes() {
|
|
89
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
90
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
91
|
+
if (!apiUrl || !apiKey) {
|
|
92
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
282
95
|
try {
|
|
283
96
|
console.log("Fetching data types...");
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
if (dataTypes.length === 0) {
|
|
97
|
+
const dataTypes = await manageApiFetch(apiUrl, apiKey, "GET", "/data-types");
|
|
98
|
+
if (!Array.isArray(dataTypes) || dataTypes.length === 0) {
|
|
287
99
|
console.log("No data types found.");
|
|
288
100
|
return;
|
|
289
101
|
}
|
|
@@ -307,11 +119,16 @@ Found ${dataTypes.length} data type(s):
|
|
|
307
119
|
|
|
308
120
|
// src/commands/list-forms.ts
|
|
309
121
|
async function listForms() {
|
|
122
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
123
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
124
|
+
if (!apiUrl || !apiKey) {
|
|
125
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
310
128
|
try {
|
|
311
129
|
console.log("Fetching form definitions...");
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
if (forms.length === 0) {
|
|
130
|
+
const forms = await manageApiFetch(apiUrl, apiKey, "GET", "/form-definitions");
|
|
131
|
+
if (!Array.isArray(forms) || forms.length === 0) {
|
|
315
132
|
console.log("No form definitions found.");
|
|
316
133
|
return;
|
|
317
134
|
}
|
|
@@ -335,10 +152,16 @@ Found ${forms.length} form definition(s):
|
|
|
335
152
|
|
|
336
153
|
// src/commands/list-components.ts
|
|
337
154
|
async function listComponents() {
|
|
155
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
156
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
157
|
+
if (!apiUrl || !apiKey) {
|
|
158
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
338
161
|
try {
|
|
339
162
|
console.log("Fetching custom components...");
|
|
340
|
-
const
|
|
341
|
-
const components = Array.isArray(
|
|
163
|
+
const result = await manageApiFetch(apiUrl, apiKey, "GET", "/components");
|
|
164
|
+
const components = Array.isArray(result) ? result : result.components || [];
|
|
342
165
|
if (components.length === 0) {
|
|
343
166
|
console.log("No custom components found.");
|
|
344
167
|
return;
|
|
@@ -360,84 +183,66 @@ Found ${components.length} custom component(s):
|
|
|
360
183
|
}
|
|
361
184
|
}
|
|
362
185
|
|
|
363
|
-
// src/commands/
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return JSON.parse(args[jsonIdx + 1]);
|
|
371
|
-
} catch {
|
|
372
|
-
console.error("Error: Invalid JSON string provided to --json");
|
|
373
|
-
process.exit(1);
|
|
374
|
-
}
|
|
186
|
+
// src/commands/create-collection.ts
|
|
187
|
+
async function createCollection(options) {
|
|
188
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
189
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
190
|
+
if (!apiUrl || !apiKey) {
|
|
191
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
192
|
+
process.exit(1);
|
|
375
193
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
console.error(
|
|
382
|
-
console.error(err instanceof Error ? err.message : err);
|
|
194
|
+
let input;
|
|
195
|
+
if (options.json || options.file) {
|
|
196
|
+
input = readJsonInput(options);
|
|
197
|
+
} else {
|
|
198
|
+
if (!options.type || !options.slug || !options.name) {
|
|
199
|
+
console.error("Error: --json or --type, --slug, --name are required");
|
|
383
200
|
process.exit(1);
|
|
384
201
|
}
|
|
202
|
+
input = {
|
|
203
|
+
type: options.type,
|
|
204
|
+
slug: options.slug,
|
|
205
|
+
name: options.name,
|
|
206
|
+
description: options.description ?? void 0
|
|
207
|
+
};
|
|
385
208
|
}
|
|
386
|
-
console.error("Error: --json or --file is required");
|
|
387
|
-
process.exit(1);
|
|
388
|
-
}
|
|
389
|
-
async function manageApiFetch(apiUrl, apiKey, method, path2, body) {
|
|
390
|
-
const url = `${apiUrl}/api/v1/sdk/manage${path2}`;
|
|
391
|
-
const response = await fetch(url, {
|
|
392
|
-
method,
|
|
393
|
-
headers: {
|
|
394
|
-
"Content-Type": "application/json",
|
|
395
|
-
Authorization: `Bearer ${apiKey}`
|
|
396
|
-
},
|
|
397
|
-
...body ? { body: JSON.stringify(body) } : {}
|
|
398
|
-
});
|
|
399
|
-
if (!response.ok) {
|
|
400
|
-
const errorBody = await response.text();
|
|
401
|
-
let errorMsg;
|
|
402
|
-
try {
|
|
403
|
-
const parsed = JSON.parse(errorBody);
|
|
404
|
-
errorMsg = parsed.error || errorBody;
|
|
405
|
-
} catch {
|
|
406
|
-
errorMsg = errorBody;
|
|
407
|
-
}
|
|
408
|
-
throw new Error(`API error (${response.status}): ${errorMsg}`);
|
|
409
|
-
}
|
|
410
|
-
return response.json();
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// src/commands/create-collection.ts
|
|
414
|
-
async function createCollection(args, apiUrl, apiKey) {
|
|
415
|
-
const input = readJsonInput(args);
|
|
416
209
|
if (!input.type || !input.slug || !input.name) {
|
|
417
210
|
console.error("Error: JSON must include type, slug, and name");
|
|
418
211
|
process.exit(1);
|
|
419
212
|
}
|
|
420
213
|
console.log(`Creating collection: ${input.slug} (${input.type})...`);
|
|
421
|
-
const
|
|
214
|
+
const result = await manageApiFetch(apiUrl, apiKey, "POST", "/collections", input);
|
|
422
215
|
console.log("Collection created successfully:");
|
|
423
|
-
console.log(JSON.stringify(
|
|
216
|
+
console.log(JSON.stringify(result, null, 2));
|
|
424
217
|
}
|
|
425
218
|
|
|
426
219
|
// src/commands/create-data-type.ts
|
|
427
|
-
async function createDataType(
|
|
428
|
-
const
|
|
220
|
+
async function createDataType(options) {
|
|
221
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
222
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
223
|
+
if (!apiUrl || !apiKey) {
|
|
224
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
const input = readJsonInput(options);
|
|
429
228
|
if (!input.slug || !input.name || !input.fields) {
|
|
430
229
|
console.error("Error: JSON must include slug, name, and fields");
|
|
431
230
|
process.exit(1);
|
|
432
231
|
}
|
|
433
232
|
console.log(`Creating data type: ${input.slug}...`);
|
|
434
|
-
const
|
|
233
|
+
const result = await manageApiFetch(apiUrl, apiKey, "POST", "/data-types", input);
|
|
435
234
|
console.log("Data type created successfully:");
|
|
436
|
-
console.log(JSON.stringify(
|
|
235
|
+
console.log(JSON.stringify(result, null, 2));
|
|
437
236
|
}
|
|
438
237
|
|
|
439
238
|
// src/commands/create-form.ts
|
|
440
239
|
async function createForm(options) {
|
|
240
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
241
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
242
|
+
if (!apiUrl || !apiKey) {
|
|
243
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
441
246
|
try {
|
|
442
247
|
let data;
|
|
443
248
|
if (options.json) {
|
|
@@ -451,13 +256,12 @@ async function createForm(options) {
|
|
|
451
256
|
name: options.name,
|
|
452
257
|
description: options.description,
|
|
453
258
|
fields: []
|
|
454
|
-
// JSONでフィールドを指定する必要がある
|
|
455
259
|
};
|
|
456
260
|
}
|
|
457
261
|
console.log(`Creating form definition: ${data.name} (${data.slug})...`);
|
|
458
|
-
const
|
|
262
|
+
const result = await manageApiFetch(apiUrl, apiKey, "POST", "/form-definitions", data);
|
|
459
263
|
console.log("\u2713 Form definition created successfully");
|
|
460
|
-
console.log(JSON.stringify(
|
|
264
|
+
console.log(JSON.stringify(result, null, 2));
|
|
461
265
|
} catch (error) {
|
|
462
266
|
console.error("\u2717 Failed to create form definition:");
|
|
463
267
|
console.error(error instanceof Error ? error.message : String(error));
|
|
@@ -467,6 +271,12 @@ async function createForm(options) {
|
|
|
467
271
|
|
|
468
272
|
// src/commands/update-collection.ts
|
|
469
273
|
async function updateCollection(options) {
|
|
274
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
275
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
276
|
+
if (!apiUrl || !apiKey) {
|
|
277
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
470
280
|
try {
|
|
471
281
|
if (!options.slug) {
|
|
472
282
|
throw new Error("--slug is required (the current slug of the collection to update)");
|
|
@@ -484,7 +294,7 @@ async function updateCollection(options) {
|
|
|
484
294
|
throw new Error("No fields to update. Provide --json, --new-slug, --name, or --description");
|
|
485
295
|
}
|
|
486
296
|
console.log(`Updating collection: ${options.slug}...`);
|
|
487
|
-
const result = await
|
|
297
|
+
const result = await manageApiFetch(apiUrl, apiKey, "PUT", `/collections/${options.slug}`, data);
|
|
488
298
|
console.log("\u2713 Collection updated successfully");
|
|
489
299
|
console.log(JSON.stringify(result, null, 2));
|
|
490
300
|
} catch (error) {
|
|
@@ -496,6 +306,12 @@ async function updateCollection(options) {
|
|
|
496
306
|
|
|
497
307
|
// src/commands/update-data-type.ts
|
|
498
308
|
async function updateDataType(options) {
|
|
309
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
310
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
311
|
+
if (!apiUrl || !apiKey) {
|
|
312
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
499
315
|
try {
|
|
500
316
|
if (!options.slug) {
|
|
501
317
|
throw new Error("--slug is required (the current slug of the data type to update)");
|
|
@@ -513,7 +329,7 @@ async function updateDataType(options) {
|
|
|
513
329
|
throw new Error("No fields to update. Provide --json, --new-slug, --name, or --description");
|
|
514
330
|
}
|
|
515
331
|
console.log(`Updating data type: ${options.slug}...`);
|
|
516
|
-
const result = await
|
|
332
|
+
const result = await manageApiFetch(apiUrl, apiKey, "PUT", `/data-types/${options.slug}`, data);
|
|
517
333
|
console.log("\u2713 Data type updated successfully");
|
|
518
334
|
console.log(JSON.stringify(result, null, 2));
|
|
519
335
|
} catch (error) {
|
|
@@ -525,6 +341,12 @@ async function updateDataType(options) {
|
|
|
525
341
|
|
|
526
342
|
// src/commands/update-form.ts
|
|
527
343
|
async function updateForm(options) {
|
|
344
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
345
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
346
|
+
if (!apiUrl || !apiKey) {
|
|
347
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
528
350
|
try {
|
|
529
351
|
if (!options.slug) {
|
|
530
352
|
throw new Error("--slug is required (the current slug of the form to update)");
|
|
@@ -542,7 +364,7 @@ async function updateForm(options) {
|
|
|
542
364
|
throw new Error("No fields to update. Provide --json, --new-slug, --name, or --description");
|
|
543
365
|
}
|
|
544
366
|
console.log(`Updating form: ${options.slug}...`);
|
|
545
|
-
const result = await
|
|
367
|
+
const result = await manageApiFetch(apiUrl, apiKey, "PUT", `/form-definitions/${options.slug}`, data);
|
|
546
368
|
console.log("\u2713 Form updated successfully");
|
|
547
369
|
console.log(JSON.stringify(result, null, 2));
|
|
548
370
|
} catch (error) {
|
|
@@ -559,14 +381,20 @@ async function confirm(message) {
|
|
|
559
381
|
input: process.stdin,
|
|
560
382
|
output: process.stdout
|
|
561
383
|
});
|
|
562
|
-
return new Promise((
|
|
384
|
+
return new Promise((resolve2) => {
|
|
563
385
|
rl.question(`${message} (y/N): `, (answer) => {
|
|
564
386
|
rl.close();
|
|
565
|
-
|
|
387
|
+
resolve2(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
566
388
|
});
|
|
567
389
|
});
|
|
568
390
|
}
|
|
569
391
|
async function deleteCollection(options) {
|
|
392
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
393
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
394
|
+
if (!apiUrl || !apiKey) {
|
|
395
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
570
398
|
try {
|
|
571
399
|
if (!options.slug) {
|
|
572
400
|
throw new Error("--slug is required");
|
|
@@ -582,7 +410,7 @@ async function deleteCollection(options) {
|
|
|
582
410
|
}
|
|
583
411
|
}
|
|
584
412
|
console.log(`Deleting collection: ${options.slug}...`);
|
|
585
|
-
await
|
|
413
|
+
await manageApiFetch(apiUrl, apiKey, "DELETE", `/collections/${options.slug}`);
|
|
586
414
|
console.log("\u2713 Collection deleted successfully");
|
|
587
415
|
} catch (error) {
|
|
588
416
|
console.error("\u2717 Failed to delete collection:");
|
|
@@ -598,14 +426,20 @@ async function confirm2(message) {
|
|
|
598
426
|
input: process.stdin,
|
|
599
427
|
output: process.stdout
|
|
600
428
|
});
|
|
601
|
-
return new Promise((
|
|
429
|
+
return new Promise((resolve2) => {
|
|
602
430
|
rl.question(`${message} (y/N): `, (answer) => {
|
|
603
431
|
rl.close();
|
|
604
|
-
|
|
432
|
+
resolve2(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
605
433
|
});
|
|
606
434
|
});
|
|
607
435
|
}
|
|
608
436
|
async function deleteDataType(options) {
|
|
437
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
438
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
439
|
+
if (!apiUrl || !apiKey) {
|
|
440
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
609
443
|
try {
|
|
610
444
|
if (!options.slug) {
|
|
611
445
|
throw new Error("--slug is required");
|
|
@@ -621,7 +455,7 @@ async function deleteDataType(options) {
|
|
|
621
455
|
}
|
|
622
456
|
}
|
|
623
457
|
console.log(`Deleting data type: ${options.slug}...`);
|
|
624
|
-
await
|
|
458
|
+
await manageApiFetch(apiUrl, apiKey, "DELETE", `/data-types/${options.slug}`);
|
|
625
459
|
console.log("\u2713 Data type deleted successfully");
|
|
626
460
|
} catch (error) {
|
|
627
461
|
console.error("\u2717 Failed to delete data type:");
|
|
@@ -637,14 +471,20 @@ async function confirm3(message) {
|
|
|
637
471
|
input: process.stdin,
|
|
638
472
|
output: process.stdout
|
|
639
473
|
});
|
|
640
|
-
return new Promise((
|
|
474
|
+
return new Promise((resolve2) => {
|
|
641
475
|
rl.question(`${message} (y/N): `, (answer) => {
|
|
642
476
|
rl.close();
|
|
643
|
-
|
|
477
|
+
resolve2(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
644
478
|
});
|
|
645
479
|
});
|
|
646
480
|
}
|
|
647
481
|
async function deleteForm(options) {
|
|
482
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
483
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
484
|
+
if (!apiUrl || !apiKey) {
|
|
485
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
648
488
|
try {
|
|
649
489
|
if (!options.slug) {
|
|
650
490
|
throw new Error("--slug is required");
|
|
@@ -661,7 +501,7 @@ async function deleteForm(options) {
|
|
|
661
501
|
}
|
|
662
502
|
}
|
|
663
503
|
console.log(`Deleting form: ${options.slug}...`);
|
|
664
|
-
await
|
|
504
|
+
await manageApiFetch(apiUrl, apiKey, "DELETE", `/form-definitions/${options.slug}`);
|
|
665
505
|
console.log("\u2713 Form deleted successfully");
|
|
666
506
|
} catch (error) {
|
|
667
507
|
console.error("\u2717 Failed to delete form:");
|
|
@@ -674,10 +514,6 @@ async function deleteForm(options) {
|
|
|
674
514
|
import { readdirSync, readFileSync as readFileSync2, existsSync } from "fs";
|
|
675
515
|
import { join } from "path";
|
|
676
516
|
import { execSync } from "child_process";
|
|
677
|
-
function parseArg(args, flag) {
|
|
678
|
-
const idx = args.indexOf(flag);
|
|
679
|
-
return idx !== -1 && args[idx + 1] ? args[idx + 1] : void 0;
|
|
680
|
-
}
|
|
681
517
|
function detectEnvironment() {
|
|
682
518
|
try {
|
|
683
519
|
const branch = execSync("git branch --show-current", { encoding: "utf-8" }).trim();
|
|
@@ -731,8 +567,14 @@ function readComponentDefinitions(componentsDir, environment) {
|
|
|
731
567
|
}
|
|
732
568
|
return components;
|
|
733
569
|
}
|
|
734
|
-
async function syncComponents(
|
|
735
|
-
const
|
|
570
|
+
async function syncComponents(options) {
|
|
571
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
572
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
573
|
+
if (!apiUrl || !apiKey) {
|
|
574
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
575
|
+
process.exit(1);
|
|
576
|
+
}
|
|
577
|
+
const environment = options.environment || detectEnvironment();
|
|
736
578
|
const componentsDir = join(process.cwd(), "cmx/components");
|
|
737
579
|
console.log(`Environment: ${environment}`);
|
|
738
580
|
const components = readComponentDefinitions(componentsDir, environment);
|
|
@@ -757,158 +599,1058 @@ async function syncComponents(args, apiUrl, apiKey) {
|
|
|
757
599
|
if (result.updated) console.log(` Updated: ${result.updated}`);
|
|
758
600
|
}
|
|
759
601
|
|
|
760
|
-
// src/
|
|
761
|
-
import
|
|
762
|
-
import
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
602
|
+
// src/codegen/generator.ts
|
|
603
|
+
import { mkdir, writeFile, rm } from "fs/promises";
|
|
604
|
+
import { join as join2 } from "path";
|
|
605
|
+
|
|
606
|
+
// src/codegen/fetch-schema.ts
|
|
607
|
+
async function fetchSchema(apiUrl, apiKey) {
|
|
608
|
+
const url = `${apiUrl.replace(/\/$/, "")}/api/v1/sdk/schema`;
|
|
609
|
+
const response = await fetch(url, {
|
|
610
|
+
method: "GET",
|
|
611
|
+
headers: {
|
|
612
|
+
"Content-Type": "application/json",
|
|
613
|
+
Authorization: `Bearer ${apiKey}`
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
if (!response.ok) {
|
|
617
|
+
const body = await response.text().catch(() => "");
|
|
618
|
+
throw new Error(`Failed to fetch schema (HTTP ${response.status}): ${body}`);
|
|
619
|
+
}
|
|
620
|
+
return response.json();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/codegen/naming.ts
|
|
624
|
+
function slugToPascalCase(slug) {
|
|
625
|
+
return slug.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
626
|
+
}
|
|
627
|
+
function slugToCamelCase(slug) {
|
|
628
|
+
const pascal = slugToPascalCase(slug);
|
|
629
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
630
|
+
}
|
|
631
|
+
function singularize(name) {
|
|
632
|
+
if (name.endsWith("ies")) {
|
|
633
|
+
return name.slice(0, -3) + "y";
|
|
634
|
+
}
|
|
635
|
+
if (name.endsWith("ses") || name.endsWith("xes") || name.endsWith("zes")) {
|
|
636
|
+
return name.slice(0, -2);
|
|
637
|
+
}
|
|
638
|
+
if (name.endsWith("s") && !name.endsWith("ss")) {
|
|
639
|
+
return name.slice(0, -1);
|
|
640
|
+
}
|
|
641
|
+
return name;
|
|
642
|
+
}
|
|
643
|
+
function sanitizeIdentifier(slug) {
|
|
644
|
+
const cleaned = slug.replace(/[^a-zA-Z0-9-]/g, "");
|
|
645
|
+
if (/^[0-9]/.test(cleaned)) {
|
|
646
|
+
return "_" + cleaned;
|
|
647
|
+
}
|
|
648
|
+
return cleaned;
|
|
649
|
+
}
|
|
650
|
+
function escapeTs(value) {
|
|
651
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
652
|
+
}
|
|
653
|
+
function createNameTracker() {
|
|
654
|
+
const used = /* @__PURE__ */ new Set();
|
|
655
|
+
return function getUniqueName(name) {
|
|
656
|
+
let candidate = name;
|
|
657
|
+
let counter = 1;
|
|
658
|
+
while (used.has(candidate)) {
|
|
659
|
+
candidate = `${name}${counter}`;
|
|
660
|
+
counter++;
|
|
661
|
+
}
|
|
662
|
+
used.add(candidate);
|
|
663
|
+
return candidate;
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/codegen/type-map.ts
|
|
668
|
+
function fieldToTsType(field) {
|
|
669
|
+
const baseType = getBaseType(field);
|
|
670
|
+
if (!field.required) {
|
|
671
|
+
return `${baseType} | null`;
|
|
672
|
+
}
|
|
673
|
+
return baseType;
|
|
674
|
+
}
|
|
675
|
+
function getBaseType(field) {
|
|
676
|
+
switch (field.type) {
|
|
677
|
+
case "text":
|
|
678
|
+
case "textarea":
|
|
679
|
+
case "richtext":
|
|
680
|
+
case "url":
|
|
681
|
+
case "email":
|
|
682
|
+
return "string";
|
|
683
|
+
case "number":
|
|
684
|
+
return "number";
|
|
685
|
+
case "boolean":
|
|
686
|
+
return "boolean";
|
|
687
|
+
case "date":
|
|
688
|
+
case "datetime":
|
|
689
|
+
return "string";
|
|
690
|
+
case "select":
|
|
691
|
+
if (field.options?.choices && field.options.choices.length > 0) {
|
|
692
|
+
return field.options.choices.map((c) => `"${c.value}"`).join(" | ");
|
|
693
|
+
}
|
|
694
|
+
return "string";
|
|
695
|
+
case "multiselect":
|
|
696
|
+
if (field.options?.choices && field.options.choices.length > 0) {
|
|
697
|
+
return `(${field.options.choices.map((c) => `"${c.value}"`).join(" | ")})[]`;
|
|
698
|
+
}
|
|
699
|
+
return "string[]";
|
|
700
|
+
case "image":
|
|
701
|
+
case "file":
|
|
702
|
+
return "string";
|
|
703
|
+
case "relation":
|
|
704
|
+
if (field.options?.multiple) {
|
|
705
|
+
return "string[]";
|
|
706
|
+
}
|
|
707
|
+
return "string";
|
|
708
|
+
case "json":
|
|
709
|
+
return "unknown";
|
|
710
|
+
default:
|
|
711
|
+
return "unknown";
|
|
712
|
+
}
|
|
798
713
|
}
|
|
714
|
+
|
|
715
|
+
// src/codegen/generate-data-type.ts
|
|
799
716
|
function generateDataTypeFile(dataType) {
|
|
800
|
-
const
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
717
|
+
const safeSlug = sanitizeIdentifier(dataType.slug);
|
|
718
|
+
const pascalPlural = slugToPascalCase(safeSlug);
|
|
719
|
+
const interfaceName = singularize(pascalPlural);
|
|
720
|
+
const slug = dataType.slug;
|
|
721
|
+
const lines = [];
|
|
722
|
+
lines.push(`// Auto-generated by cmx-sdk generate`);
|
|
723
|
+
lines.push(`// Data Type: ${dataType.name} (${slug})`);
|
|
724
|
+
lines.push(`// Do not edit manually.`);
|
|
725
|
+
lines.push(``);
|
|
726
|
+
lines.push(`import { getDataEntries, getDataEntry } from "cmx-sdk"`);
|
|
727
|
+
lines.push(``);
|
|
728
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
729
|
+
for (const field of dataType.fields) {
|
|
730
|
+
const tsType = fieldToTsType(field);
|
|
731
|
+
const docParts = [];
|
|
732
|
+
if (field.label) docParts.push(field.label);
|
|
733
|
+
if (field.description) docParts.push(field.description);
|
|
734
|
+
if (docParts.length > 0) {
|
|
735
|
+
lines.push(` /** ${docParts.join(" - ")} */`);
|
|
736
|
+
}
|
|
737
|
+
lines.push(` ${field.key}: ${tsType}`);
|
|
738
|
+
}
|
|
739
|
+
lines.push(` _meta: {`);
|
|
740
|
+
lines.push(` sortOrder: number`);
|
|
741
|
+
lines.push(` createdAt: string`);
|
|
742
|
+
lines.push(` updatedAt: string`);
|
|
743
|
+
lines.push(` }`);
|
|
744
|
+
lines.push(`}`);
|
|
745
|
+
lines.push(``);
|
|
746
|
+
const getAllFnName = `get${pascalPlural}`;
|
|
747
|
+
lines.push(`export async function ${getAllFnName}(options?: {`);
|
|
748
|
+
lines.push(` sortBy?: string`);
|
|
749
|
+
lines.push(` sortOrder?: string`);
|
|
750
|
+
lines.push(` limit?: number`);
|
|
751
|
+
lines.push(` offset?: number`);
|
|
752
|
+
lines.push(`}): Promise<{ type: string; items: ${interfaceName}[]; count: number }> {`);
|
|
753
|
+
lines.push(` const result = await getDataEntries("${slug}", options)`);
|
|
754
|
+
lines.push(
|
|
755
|
+
` return result as unknown as { type: string; items: ${interfaceName}[]; count: number }`
|
|
756
|
+
);
|
|
757
|
+
lines.push(`}`);
|
|
758
|
+
lines.push(``);
|
|
759
|
+
const getOneFnName = `get${interfaceName}ById`;
|
|
760
|
+
lines.push(
|
|
761
|
+
`export async function ${getOneFnName}(id: string): Promise<${interfaceName}> {`
|
|
762
|
+
);
|
|
763
|
+
lines.push(` const result = await getDataEntry("${slug}", id)`);
|
|
764
|
+
lines.push(` return result as unknown as ${interfaceName}`);
|
|
765
|
+
lines.push(`}`);
|
|
766
|
+
lines.push(``);
|
|
767
|
+
return lines.join("\n");
|
|
768
|
+
}
|
|
809
769
|
|
|
810
|
-
|
|
770
|
+
// src/codegen/generate-collection.ts
|
|
771
|
+
function generateCollectionFile(collection) {
|
|
772
|
+
const safeSlug = sanitizeIdentifier(collection.slug);
|
|
773
|
+
const pascal = slugToPascalCase(safeSlug);
|
|
774
|
+
const slug = collection.slug;
|
|
775
|
+
const lines = [];
|
|
776
|
+
const escapedName = escapeTs(collection.name);
|
|
777
|
+
lines.push(`// Auto-generated by cmx-sdk generate`);
|
|
778
|
+
lines.push(`// Collection: ${escapedName} (${slug})`);
|
|
779
|
+
lines.push(`// Do not edit manually.`);
|
|
780
|
+
lines.push(``);
|
|
781
|
+
lines.push(
|
|
782
|
+
`import { getCollectionContents, getCollectionContentDetail } from "cmx-sdk"`
|
|
783
|
+
);
|
|
784
|
+
lines.push(
|
|
785
|
+
`import type { CollectionContentsResponse, CollectionContentDetailResponse } from "cmx-sdk"`
|
|
786
|
+
);
|
|
787
|
+
lines.push(``);
|
|
788
|
+
lines.push(
|
|
789
|
+
`export async function get${pascal}Contents(): Promise<CollectionContentsResponse> {`
|
|
790
|
+
);
|
|
791
|
+
lines.push(` return getCollectionContents("${escapeTs(slug)}")`);
|
|
792
|
+
lines.push(`}`);
|
|
793
|
+
lines.push(``);
|
|
794
|
+
lines.push(
|
|
795
|
+
`export async function get${pascal}ContentDetail(slug: string): Promise<CollectionContentDetailResponse> {`
|
|
796
|
+
);
|
|
797
|
+
lines.push(` return getCollectionContentDetail("${escapeTs(slug)}", slug)`);
|
|
798
|
+
lines.push(`}`);
|
|
799
|
+
lines.push(``);
|
|
800
|
+
return lines.join("\n");
|
|
801
|
+
}
|
|
811
802
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
803
|
+
// src/codegen/zod-map.ts
|
|
804
|
+
function fieldToZodType(field) {
|
|
805
|
+
const base = getBaseZodType(field);
|
|
806
|
+
if (!field.required) {
|
|
807
|
+
return `${base}.optional()`;
|
|
808
|
+
}
|
|
809
|
+
return base;
|
|
810
|
+
}
|
|
811
|
+
function getBaseZodType(field) {
|
|
812
|
+
switch (field.type) {
|
|
813
|
+
case "text":
|
|
814
|
+
case "textarea":
|
|
815
|
+
case "richtext":
|
|
816
|
+
return field.required ? `z.string().min(1, "${escapeTs(field.label)}\u306F\u5FC5\u9808\u3067\u3059")` : `z.string()`;
|
|
817
|
+
case "email":
|
|
818
|
+
return `z.string().email("\u6709\u52B9\u306A\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044")`;
|
|
819
|
+
case "url":
|
|
820
|
+
return `z.string().url("\u6709\u52B9\u306AURL\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044")`;
|
|
821
|
+
case "number":
|
|
822
|
+
return `z.number()`;
|
|
823
|
+
case "boolean":
|
|
824
|
+
return `z.boolean()`;
|
|
825
|
+
case "date":
|
|
826
|
+
case "datetime":
|
|
827
|
+
return `z.string()`;
|
|
828
|
+
case "select":
|
|
829
|
+
if (field.options?.choices && field.options.choices.length > 0) {
|
|
830
|
+
const values = field.options.choices.map((c) => `"${escapeTs(c.value)}"`).join(", ");
|
|
831
|
+
return `z.enum([${values}])`;
|
|
832
|
+
}
|
|
833
|
+
return `z.string()`;
|
|
834
|
+
case "multiselect":
|
|
835
|
+
if (field.options?.choices && field.options.choices.length > 0) {
|
|
836
|
+
const values = field.options.choices.map((c) => `"${escapeTs(c.value)}"`).join(", ");
|
|
837
|
+
return `z.array(z.enum([${values}]))`;
|
|
838
|
+
}
|
|
839
|
+
return `z.array(z.string())`;
|
|
840
|
+
case "image":
|
|
841
|
+
case "file":
|
|
842
|
+
return `z.string()`;
|
|
843
|
+
case "relation":
|
|
844
|
+
if (field.options?.multiple) {
|
|
845
|
+
return `z.array(z.string())`;
|
|
846
|
+
}
|
|
847
|
+
return `z.string()`;
|
|
848
|
+
case "json":
|
|
849
|
+
return `z.unknown()`;
|
|
850
|
+
default:
|
|
851
|
+
return `z.unknown()`;
|
|
818
852
|
}
|
|
819
853
|
}
|
|
820
854
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
const
|
|
828
|
-
|
|
855
|
+
// src/codegen/generate-form.ts
|
|
856
|
+
function generateFormFile(form) {
|
|
857
|
+
const safeSlug = sanitizeIdentifier(form.slug);
|
|
858
|
+
const pascal = slugToPascalCase(safeSlug);
|
|
859
|
+
const camel = slugToCamelCase(safeSlug);
|
|
860
|
+
const slug = form.slug;
|
|
861
|
+
const lines = [];
|
|
862
|
+
const escapedName = escapeTs(form.name);
|
|
863
|
+
lines.push(`// Auto-generated by cmx-sdk generate`);
|
|
864
|
+
lines.push(`// Form: ${escapedName} (${slug})`);
|
|
865
|
+
lines.push(`// Do not edit manually.`);
|
|
866
|
+
lines.push(``);
|
|
867
|
+
lines.push(`import { z } from "zod"`);
|
|
868
|
+
lines.push(`import { submitFormData } from "cmx-sdk"`);
|
|
869
|
+
lines.push(``);
|
|
870
|
+
lines.push(`export const ${camel}Schema = z.object({`);
|
|
871
|
+
for (const field of form.fields) {
|
|
872
|
+
const zodType = fieldToZodType(field);
|
|
873
|
+
if (field.label) {
|
|
874
|
+
lines.push(` /** ${field.label.replace(/\*\//g, "* /")} */`);
|
|
875
|
+
}
|
|
876
|
+
lines.push(` ${field.key}: ${zodType},`);
|
|
877
|
+
}
|
|
878
|
+
lines.push(`})`);
|
|
879
|
+
lines.push(``);
|
|
880
|
+
lines.push(`export type ${pascal}FormData = z.infer<typeof ${camel}Schema>`);
|
|
881
|
+
lines.push(``);
|
|
882
|
+
lines.push(
|
|
883
|
+
`export async function submit${pascal}(data: ${pascal}FormData): Promise<{ success: true; id: string }> {`
|
|
884
|
+
);
|
|
885
|
+
lines.push(` const validated = ${camel}Schema.parse(data)`);
|
|
886
|
+
lines.push(` return submitFormData("${escapeTs(slug)}", validated)`);
|
|
887
|
+
lines.push(`}`);
|
|
888
|
+
lines.push(``);
|
|
889
|
+
return lines.join("\n");
|
|
829
890
|
}
|
|
830
891
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
892
|
+
// src/codegen/generate-index.ts
|
|
893
|
+
function generateDataTypesIndex(slugs) {
|
|
894
|
+
const lines = [
|
|
895
|
+
`// Auto-generated by cmx-sdk generate`,
|
|
896
|
+
`// Do not edit manually.`,
|
|
897
|
+
``
|
|
898
|
+
];
|
|
899
|
+
for (const slug of slugs) {
|
|
900
|
+
lines.push(`export * from "./${slug}"`);
|
|
901
|
+
}
|
|
902
|
+
lines.push(``);
|
|
903
|
+
return lines.join("\n");
|
|
904
|
+
}
|
|
905
|
+
function generateCollectionsIndex(slugs) {
|
|
906
|
+
const lines = [
|
|
907
|
+
`// Auto-generated by cmx-sdk generate`,
|
|
908
|
+
`// Do not edit manually.`,
|
|
909
|
+
``
|
|
910
|
+
];
|
|
911
|
+
for (const slug of slugs) {
|
|
912
|
+
lines.push(`export * from "./${slug}"`);
|
|
913
|
+
}
|
|
914
|
+
lines.push(``);
|
|
915
|
+
return lines.join("\n");
|
|
916
|
+
}
|
|
917
|
+
function generateFormsIndex(slugs) {
|
|
918
|
+
const lines = [
|
|
919
|
+
`// Auto-generated by cmx-sdk generate`,
|
|
920
|
+
`// Do not edit manually.`,
|
|
921
|
+
``
|
|
922
|
+
];
|
|
923
|
+
for (const slug of slugs) {
|
|
924
|
+
lines.push(`export * from "./${slug}"`);
|
|
925
|
+
}
|
|
926
|
+
lines.push(``);
|
|
927
|
+
return lines.join("\n");
|
|
928
|
+
}
|
|
929
|
+
function generateRootIndex(hasDataTypes, hasCollections, hasForms) {
|
|
930
|
+
const lines = [
|
|
931
|
+
`// Auto-generated by cmx-sdk generate`,
|
|
932
|
+
`// Do not edit manually.`,
|
|
933
|
+
``
|
|
934
|
+
];
|
|
935
|
+
if (hasDataTypes) {
|
|
936
|
+
lines.push(`export * from "./data-types"`);
|
|
937
|
+
}
|
|
938
|
+
if (hasCollections) {
|
|
939
|
+
lines.push(`export * from "./collections"`);
|
|
940
|
+
}
|
|
941
|
+
if (hasForms) {
|
|
942
|
+
lines.push(`export * from "./forms"`);
|
|
943
|
+
}
|
|
944
|
+
lines.push(``);
|
|
945
|
+
return lines.join("\n");
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// src/codegen/generator.ts
|
|
949
|
+
async function generate(options) {
|
|
950
|
+
const { apiUrl, apiKey, outputDir } = options;
|
|
951
|
+
console.log(`Fetching schema from ${apiUrl} ...`);
|
|
952
|
+
const schema = await fetchSchema(apiUrl, apiKey);
|
|
953
|
+
const forms = schema.forms ?? [];
|
|
954
|
+
console.log(
|
|
955
|
+
`Found ${schema.dataTypes.length} data type(s), ${schema.collections.length} collection(s), and ${forms.length} form(s)`
|
|
956
|
+
);
|
|
957
|
+
await rm(outputDir, { recursive: true, force: true });
|
|
958
|
+
await mkdir(outputDir, { recursive: true });
|
|
959
|
+
const dataTypeSlugs = [];
|
|
960
|
+
const collectionSlugs = [];
|
|
961
|
+
const formSlugs = [];
|
|
962
|
+
if (schema.dataTypes.length > 0) {
|
|
963
|
+
const dataTypesDir = join2(outputDir, "data-types");
|
|
964
|
+
await mkdir(dataTypesDir, { recursive: true });
|
|
965
|
+
const nameTracker = createNameTracker();
|
|
966
|
+
for (const dt of schema.dataTypes) {
|
|
967
|
+
const safeSlug = sanitizeIdentifier(dt.slug);
|
|
968
|
+
if (!safeSlug) {
|
|
969
|
+
console.warn(` Skipping data type with invalid slug: "${dt.slug}"`);
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
const fileName = nameTracker(safeSlug);
|
|
973
|
+
const content = generateDataTypeFile(dt);
|
|
974
|
+
await writeFile(join2(dataTypesDir, `${fileName}.ts`), content, "utf-8");
|
|
975
|
+
dataTypeSlugs.push(fileName);
|
|
976
|
+
console.log(` Generated data-types/${fileName}.ts (${dt.name})`);
|
|
977
|
+
}
|
|
978
|
+
const indexContent = generateDataTypesIndex(dataTypeSlugs);
|
|
979
|
+
await writeFile(join2(dataTypesDir, "index.ts"), indexContent, "utf-8");
|
|
980
|
+
}
|
|
981
|
+
if (schema.collections.length > 0) {
|
|
982
|
+
const collectionsDir = join2(outputDir, "collections");
|
|
983
|
+
await mkdir(collectionsDir, { recursive: true });
|
|
984
|
+
const nameTracker = createNameTracker();
|
|
985
|
+
for (const col of schema.collections) {
|
|
986
|
+
const safeSlug = sanitizeIdentifier(col.slug);
|
|
987
|
+
if (!safeSlug) {
|
|
988
|
+
console.warn(` Skipping collection with invalid slug: "${col.slug}"`);
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
const fileName = nameTracker(safeSlug);
|
|
992
|
+
const content = generateCollectionFile(col);
|
|
993
|
+
await writeFile(join2(collectionsDir, `${fileName}.ts`), content, "utf-8");
|
|
994
|
+
collectionSlugs.push(fileName);
|
|
995
|
+
console.log(` Generated collections/${fileName}.ts (${col.name})`);
|
|
996
|
+
}
|
|
997
|
+
const indexContent = generateCollectionsIndex(collectionSlugs);
|
|
998
|
+
await writeFile(join2(collectionsDir, "index.ts"), indexContent, "utf-8");
|
|
999
|
+
}
|
|
1000
|
+
if (forms.length > 0) {
|
|
1001
|
+
const formsDir = join2(outputDir, "forms");
|
|
1002
|
+
await mkdir(formsDir, { recursive: true });
|
|
1003
|
+
const nameTracker = createNameTracker();
|
|
1004
|
+
for (const form of forms) {
|
|
1005
|
+
const safeSlug = sanitizeIdentifier(form.slug);
|
|
1006
|
+
if (!safeSlug) {
|
|
1007
|
+
console.warn(` Skipping form with invalid slug: "${form.slug}"`);
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
const fileName = nameTracker(safeSlug);
|
|
1011
|
+
const content = generateFormFile(form);
|
|
1012
|
+
await writeFile(join2(formsDir, `${fileName}.ts`), content, "utf-8");
|
|
1013
|
+
formSlugs.push(fileName);
|
|
1014
|
+
console.log(` Generated forms/${fileName}.ts (${form.name})`);
|
|
1015
|
+
}
|
|
1016
|
+
const indexContent = generateFormsIndex(formSlugs);
|
|
1017
|
+
await writeFile(join2(formsDir, "index.ts"), indexContent, "utf-8");
|
|
1018
|
+
}
|
|
1019
|
+
const rootIndex = generateRootIndex(
|
|
1020
|
+
dataTypeSlugs.length > 0,
|
|
1021
|
+
collectionSlugs.length > 0,
|
|
1022
|
+
formSlugs.length > 0
|
|
1023
|
+
);
|
|
1024
|
+
await writeFile(join2(outputDir, "index.ts"), rootIndex, "utf-8");
|
|
1025
|
+
console.log(`
|
|
1026
|
+
Generated files in ${outputDir}`);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// src/commands/list-collection-data-types.ts
|
|
1030
|
+
async function listCollectionDataTypes(opts) {
|
|
1031
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
1032
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
1033
|
+
if (!apiUrl || !apiKey) {
|
|
1034
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
const collectionSlug = opts.collection;
|
|
1038
|
+
if (!collectionSlug) {
|
|
1039
|
+
console.error("Error: --collection is required");
|
|
1040
|
+
process.exit(1);
|
|
1041
|
+
}
|
|
1042
|
+
try {
|
|
1043
|
+
console.log(`Fetching data types for collection: ${collectionSlug}...`);
|
|
1044
|
+
const dataTypes = await manageApiFetch(
|
|
1045
|
+
apiUrl,
|
|
1046
|
+
apiKey,
|
|
1047
|
+
"GET",
|
|
1048
|
+
`/collections/${collectionSlug}/data-types`
|
|
1049
|
+
);
|
|
1050
|
+
if (dataTypes.length === 0) {
|
|
1051
|
+
console.log("No data types found for this collection.");
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
console.log(`
|
|
1055
|
+
Found ${dataTypes.length} data type(s):
|
|
1056
|
+
`);
|
|
1057
|
+
dataTypes.forEach((dt) => {
|
|
1058
|
+
console.log(` ${dt.name} (${dt.slug})`);
|
|
1059
|
+
if (dt.description) {
|
|
1060
|
+
console.log(` Description: ${dt.description}`);
|
|
1061
|
+
}
|
|
1062
|
+
console.log(` Fields: ${dt.fields?.length || 0}`);
|
|
1063
|
+
console.log();
|
|
1064
|
+
});
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
console.error("\u2717 Failed to list collection data types:");
|
|
1067
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1068
|
+
process.exit(1);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// src/commands/add-collection-data-type.ts
|
|
1073
|
+
async function addCollectionDataType(opts) {
|
|
1074
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
1075
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
1076
|
+
if (!apiUrl || !apiKey) {
|
|
1077
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
1078
|
+
process.exit(1);
|
|
1079
|
+
}
|
|
1080
|
+
const collectionSlug = opts.collection;
|
|
1081
|
+
if (!collectionSlug) {
|
|
1082
|
+
console.error("Error: --collection is required");
|
|
1083
|
+
process.exit(1);
|
|
1084
|
+
}
|
|
1085
|
+
try {
|
|
1086
|
+
let input;
|
|
1087
|
+
if (opts.preset) {
|
|
1088
|
+
input = { presetSlug: opts.preset };
|
|
1089
|
+
} else if (opts.json || opts.file) {
|
|
1090
|
+
input = readJsonInput(opts);
|
|
1091
|
+
} else {
|
|
1092
|
+
console.error("Error: --preset or --json/--file is required");
|
|
1093
|
+
process.exit(1);
|
|
1094
|
+
}
|
|
1095
|
+
console.log(`Adding data type to collection: ${collectionSlug}...`);
|
|
1096
|
+
const result = await manageApiFetch(
|
|
1097
|
+
apiUrl,
|
|
1098
|
+
apiKey,
|
|
1099
|
+
"POST",
|
|
1100
|
+
`/collections/${collectionSlug}/data-types`,
|
|
1101
|
+
input
|
|
1102
|
+
);
|
|
1103
|
+
console.log("Data type added successfully:");
|
|
1104
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
console.error("\u2717 Failed to add collection data type:");
|
|
1107
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1108
|
+
process.exit(1);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// src/commands/remove-collection-data-type.ts
|
|
1113
|
+
async function removeCollectionDataType(opts) {
|
|
1114
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
1115
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
1116
|
+
if (!apiUrl || !apiKey) {
|
|
1117
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
1118
|
+
process.exit(1);
|
|
1119
|
+
}
|
|
1120
|
+
const collectionSlug = opts.collection;
|
|
1121
|
+
if (!collectionSlug) {
|
|
1122
|
+
console.error("Error: --collection is required");
|
|
1123
|
+
process.exit(1);
|
|
1124
|
+
}
|
|
1125
|
+
if (!opts.dataType) {
|
|
1126
|
+
console.error("Error: --data-type is required");
|
|
1127
|
+
process.exit(1);
|
|
1128
|
+
}
|
|
1129
|
+
try {
|
|
1130
|
+
console.log(`Removing data type "${opts.dataType}" from collection: ${collectionSlug}...`);
|
|
1131
|
+
await manageApiFetch(
|
|
1132
|
+
apiUrl,
|
|
1133
|
+
apiKey,
|
|
1134
|
+
"DELETE",
|
|
1135
|
+
`/collections/${collectionSlug}/data-types/${opts.dataType}`
|
|
1136
|
+
);
|
|
1137
|
+
console.log("Data type removed successfully.");
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
console.error("\u2717 Failed to remove collection data type:");
|
|
1140
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1141
|
+
process.exit(1);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// src/commands/list-collection-presets.ts
|
|
1146
|
+
async function listCollectionPresets(opts) {
|
|
1147
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
1148
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
1149
|
+
if (!apiUrl || !apiKey) {
|
|
1150
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
1151
|
+
process.exit(1);
|
|
1152
|
+
}
|
|
1153
|
+
try {
|
|
1154
|
+
const query = opts.type ? `?type=${opts.type}` : "";
|
|
1155
|
+
console.log(opts.type ? `Fetching presets for type: ${opts.type}...` : "Fetching all presets...");
|
|
1156
|
+
const result = await manageApiFetch(
|
|
1157
|
+
apiUrl,
|
|
1158
|
+
apiKey,
|
|
1159
|
+
"GET",
|
|
1160
|
+
`/collection-presets${query}`
|
|
1161
|
+
);
|
|
1162
|
+
if (opts.type && result.recommended) {
|
|
1163
|
+
console.log(`
|
|
1164
|
+
Recommended presets for "${opts.type}":
|
|
1165
|
+
`);
|
|
1166
|
+
result.recommended.forEach((p) => {
|
|
1167
|
+
const defaultMark = p.default ? " [default]" : "";
|
|
1168
|
+
console.log(` ${p.name} (${p.slug})${defaultMark}`);
|
|
1169
|
+
console.log(` Type: ${p.referenceType}, Hierarchical: ${p.hierarchical}`);
|
|
1170
|
+
console.log(` Fields: ${p.fields.map((f) => f.key).join(", ")}`);
|
|
1171
|
+
console.log();
|
|
1172
|
+
});
|
|
1173
|
+
if (result.others && result.others.length > 0) {
|
|
1174
|
+
console.log("Other available presets:\n");
|
|
1175
|
+
result.others.forEach((p) => {
|
|
1176
|
+
console.log(` ${p.name} (${p.slug})`);
|
|
1177
|
+
console.log();
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
} else if (result.all) {
|
|
1181
|
+
console.log(`
|
|
1182
|
+
Found ${result.all.length} preset(s):
|
|
1183
|
+
`);
|
|
1184
|
+
result.all.forEach((p) => {
|
|
1185
|
+
console.log(` ${p.name} (${p.slug})`);
|
|
1186
|
+
console.log(` Type: ${p.referenceType}, Hierarchical: ${p.hierarchical}`);
|
|
1187
|
+
console.log(` Fields: ${p.fields.map((f) => f.key).join(", ")}`);
|
|
1188
|
+
console.log();
|
|
1189
|
+
});
|
|
1190
|
+
} else {
|
|
1191
|
+
console.log("No presets found.");
|
|
1192
|
+
}
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
console.error("\u2717 Failed to list presets:");
|
|
1195
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1196
|
+
process.exit(1);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/commands/link-collection-data-type.ts
|
|
1201
|
+
async function linkCollectionDataType(opts) {
|
|
1202
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
1203
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
1204
|
+
if (!apiUrl || !apiKey) {
|
|
1205
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
1206
|
+
process.exit(1);
|
|
1207
|
+
}
|
|
1208
|
+
const collectionSlug = opts.collection;
|
|
1209
|
+
if (!collectionSlug) {
|
|
1210
|
+
console.error("Error: --collection is required");
|
|
1211
|
+
process.exit(1);
|
|
1212
|
+
}
|
|
1213
|
+
if (!opts.dataType) {
|
|
1214
|
+
console.error("Error: --data-type is required");
|
|
1215
|
+
process.exit(1);
|
|
1216
|
+
}
|
|
1217
|
+
if (!opts.fieldSlug) {
|
|
1218
|
+
console.error("Error: --field-slug is required");
|
|
1219
|
+
process.exit(1);
|
|
1220
|
+
}
|
|
1221
|
+
if (!opts.label) {
|
|
1222
|
+
console.error("Error: --label is required");
|
|
1223
|
+
process.exit(1);
|
|
1224
|
+
}
|
|
1225
|
+
const referenceType = opts.referenceType || "single";
|
|
1226
|
+
if (referenceType !== "single" && referenceType !== "multiple") {
|
|
1227
|
+
console.error("Error: --reference-type must be 'single' or 'multiple'");
|
|
1228
|
+
process.exit(1);
|
|
1229
|
+
}
|
|
1230
|
+
try {
|
|
1231
|
+
console.log(`Linking global data type "${opts.dataType}" to collection "${collectionSlug}"...`);
|
|
1232
|
+
const result = await manageApiFetch(
|
|
1233
|
+
apiUrl,
|
|
1234
|
+
apiKey,
|
|
1235
|
+
"POST",
|
|
1236
|
+
`/collections/${collectionSlug}/data-types/link`,
|
|
1237
|
+
{
|
|
1238
|
+
dataTypeSlug: opts.dataType,
|
|
1239
|
+
fieldSlug: opts.fieldSlug,
|
|
1240
|
+
label: opts.label,
|
|
1241
|
+
referenceType
|
|
1242
|
+
}
|
|
1243
|
+
);
|
|
1244
|
+
console.log("Global data type linked successfully:");
|
|
1245
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
console.error("\u2717 Failed to link global data type:");
|
|
1248
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1249
|
+
process.exit(1);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/codegen/scaffolder.ts
|
|
1254
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1255
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
1256
|
+
import { join as join3, dirname, resolve } from "path";
|
|
1257
|
+
|
|
1258
|
+
// src/codegen/scaffold-collection.ts
|
|
1259
|
+
function scaffoldCollectionListPage(collection) {
|
|
1260
|
+
const safeSlug = sanitizeIdentifier(collection.slug);
|
|
1261
|
+
const pascal = slugToPascalCase(safeSlug);
|
|
1262
|
+
const escapedName = escapeTs(collection.name);
|
|
1263
|
+
const escapedDescription = escapeTs(collection.description ?? `${collection.name}\u306E\u4E00\u89A7`);
|
|
1264
|
+
return `import { get${pascal}Contents } from "@/cmx/generated"
|
|
1265
|
+
import Link from "next/link"
|
|
1266
|
+
import type { Metadata } from "next"
|
|
1267
|
+
|
|
1268
|
+
export const metadata: Metadata = {
|
|
1269
|
+
title: "${escapedName}",
|
|
1270
|
+
description: "${escapedDescription}",
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
export default async function ${pascal}Page() {
|
|
1274
|
+
const { contents } = await get${pascal}Contents()
|
|
1275
|
+
|
|
1276
|
+
return (
|
|
1277
|
+
<main>
|
|
1278
|
+
<h1>${escapedName}</h1>
|
|
1279
|
+
<ul>
|
|
1280
|
+
{contents.map((item) => (
|
|
1281
|
+
<li key={item.id}>
|
|
1282
|
+
<Link href="/${safeSlug}/\${item.slug}">
|
|
1283
|
+
<h2>{item.title}</h2>
|
|
1284
|
+
{item.description && <p>{item.description}</p>}
|
|
1285
|
+
</Link>
|
|
1286
|
+
</li>
|
|
1287
|
+
))}
|
|
1288
|
+
</ul>
|
|
1289
|
+
</main>
|
|
1290
|
+
)
|
|
834
1291
|
}
|
|
835
1292
|
`;
|
|
836
1293
|
}
|
|
837
|
-
function
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1294
|
+
function scaffoldCollectionDetailPage(collection) {
|
|
1295
|
+
const safeSlug = sanitizeIdentifier(collection.slug);
|
|
1296
|
+
const pascal = slugToPascalCase(safeSlug);
|
|
1297
|
+
return `import { get${pascal}Contents, get${pascal}ContentDetail } from "@/cmx/generated"
|
|
1298
|
+
import { renderMdx } from "cmx-sdk"
|
|
1299
|
+
import type { Metadata } from "next"
|
|
1300
|
+
|
|
1301
|
+
type Props = {
|
|
1302
|
+
params: Promise<{ slug: string }>
|
|
1303
|
+
}
|
|
842
1304
|
|
|
843
|
-
|
|
844
|
-
|
|
1305
|
+
export async function generateStaticParams() {
|
|
1306
|
+
const { contents } = await get${pascal}Contents()
|
|
1307
|
+
return contents.map((item) => ({ slug: item.slug }))
|
|
1308
|
+
}
|
|
845
1309
|
|
|
846
|
-
export async function
|
|
847
|
-
|
|
1310
|
+
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
1311
|
+
const { slug } = await params
|
|
1312
|
+
const { content } = await get${pascal}ContentDetail(slug)
|
|
1313
|
+
return {
|
|
1314
|
+
title: content.title,
|
|
1315
|
+
description: content.description ?? undefined,
|
|
1316
|
+
}
|
|
848
1317
|
}
|
|
849
1318
|
|
|
850
|
-
export async function
|
|
851
|
-
|
|
1319
|
+
export default async function ${pascal}DetailPage({ params }: Props) {
|
|
1320
|
+
const { slug } = await params
|
|
1321
|
+
const { content, references } = await get${pascal}ContentDetail(slug)
|
|
1322
|
+
const { content: rendered } = await renderMdx(content.mdx, references)
|
|
1323
|
+
|
|
1324
|
+
return (
|
|
1325
|
+
<main>
|
|
1326
|
+
<article>
|
|
1327
|
+
<h1>{content.title}</h1>
|
|
1328
|
+
{rendered}
|
|
1329
|
+
</article>
|
|
1330
|
+
</main>
|
|
1331
|
+
)
|
|
852
1332
|
}
|
|
853
1333
|
`;
|
|
854
1334
|
}
|
|
855
|
-
|
|
856
|
-
|
|
1335
|
+
|
|
1336
|
+
// src/codegen/scaffold-data-type.ts
|
|
1337
|
+
function scaffoldDataTypePage(dataType) {
|
|
1338
|
+
const safeSlug = sanitizeIdentifier(dataType.slug);
|
|
1339
|
+
const pascalPlural = slugToPascalCase(safeSlug);
|
|
1340
|
+
const interfaceName = singularize(pascalPlural);
|
|
1341
|
+
const slug = dataType.slug;
|
|
1342
|
+
const fieldComments = dataType.fields.map((f) => `// ${f.key}: ${f.type}${f.required ? "" : " (optional)"}`).join("\n");
|
|
1343
|
+
return `import { get${pascalPlural} } from "@/cmx/generated"
|
|
1344
|
+
import type { ${interfaceName} } from "@/cmx/generated"
|
|
1345
|
+
import type { Metadata } from "next"
|
|
1346
|
+
|
|
1347
|
+
export const metadata: Metadata = {
|
|
1348
|
+
title: "${escapeTs(dataType.name)}",
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Available fields:
|
|
1352
|
+
${fieldComments}
|
|
1353
|
+
|
|
1354
|
+
export default async function ${pascalPlural}Page() {
|
|
1355
|
+
const { items } = await get${pascalPlural}()
|
|
1356
|
+
|
|
1357
|
+
return (
|
|
1358
|
+
<main>
|
|
1359
|
+
<h1>${escapeTs(dataType.name)}</h1>
|
|
1360
|
+
<ul>
|
|
1361
|
+
{items.map((item: ${interfaceName}, index: number) => (
|
|
1362
|
+
<li key={index}>
|
|
1363
|
+
{/* TODO: Customize the display for each item */}
|
|
1364
|
+
<pre>{JSON.stringify(item, null, 2)}</pre>
|
|
1365
|
+
</li>
|
|
1366
|
+
))}
|
|
1367
|
+
</ul>
|
|
1368
|
+
</main>
|
|
1369
|
+
)
|
|
857
1370
|
}
|
|
858
|
-
async function generate(options) {
|
|
859
|
-
const outputDir = options.output || "cmx/generated";
|
|
860
|
-
try {
|
|
861
|
-
console.log("Fetching schema from CMX API...");
|
|
862
|
-
const response = await getSchema();
|
|
863
|
-
const schema = response.data;
|
|
864
|
-
console.log(`Found ${schema.collections.length} collections and ${schema.dataTypes.length} data types`);
|
|
865
|
-
const collectionsDir = path.join(outputDir, "collections");
|
|
866
|
-
const dataTypesDir = path.join(outputDir, "data-types");
|
|
867
|
-
fs.mkdirSync(collectionsDir, { recursive: true });
|
|
868
|
-
fs.mkdirSync(dataTypesDir, { recursive: true });
|
|
869
|
-
for (const collection of schema.collections) {
|
|
870
|
-
const filePath = path.join(collectionsDir, `${collection.slug}.ts`);
|
|
871
|
-
const content = generateCollectionFile(collection);
|
|
872
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
873
|
-
console.log(`\u2713 Generated ${filePath}`);
|
|
874
|
-
}
|
|
875
|
-
const collectionIndexContent = `// Auto-generated by cmx-sdk generate
|
|
876
|
-
// Do not edit manually.
|
|
877
|
-
|
|
878
|
-
${schema.collections.map((c) => `export * from "./${c.slug}"`).join("\n")}
|
|
879
1371
|
`;
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// src/codegen/scaffold-form.ts
|
|
1375
|
+
function scaffoldFormPage(form) {
|
|
1376
|
+
const safeSlug = sanitizeIdentifier(form.slug);
|
|
1377
|
+
const pascal = slugToPascalCase(safeSlug);
|
|
1378
|
+
const camel = slugToCamelCase(safeSlug);
|
|
1379
|
+
return `import { ${pascal}Form } from "./_components/${safeSlug}-form"
|
|
1380
|
+
import { ${camel}Schema, submit${pascal} } from "@/cmx/generated"
|
|
1381
|
+
import type { Metadata } from "next"
|
|
1382
|
+
|
|
1383
|
+
export const metadata: Metadata = {
|
|
1384
|
+
title: "${escapeTs(form.name)}",
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
async function handleSubmit(data: Record<string, unknown>) {
|
|
1388
|
+
"use server"
|
|
1389
|
+
const validated = ${camel}Schema.parse(data)
|
|
1390
|
+
return submit${pascal}(validated)
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
export default function ${pascal}Page() {
|
|
1394
|
+
return (
|
|
1395
|
+
<main>
|
|
1396
|
+
<h1>${escapeTs(form.name)}</h1>
|
|
1397
|
+
<${pascal}Form action={handleSubmit} />
|
|
1398
|
+
</main>
|
|
1399
|
+
)
|
|
1400
|
+
}
|
|
892
1401
|
`;
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1402
|
+
}
|
|
1403
|
+
function scaffoldFormComponent(form) {
|
|
1404
|
+
const safeSlug = sanitizeIdentifier(form.slug);
|
|
1405
|
+
const pascal = slugToPascalCase(safeSlug);
|
|
1406
|
+
const camel = slugToCamelCase(safeSlug);
|
|
1407
|
+
const inputFields = form.fields.map((field) => {
|
|
1408
|
+
const inputType = getInputType(field.type);
|
|
1409
|
+
const required = field.required ? " required" : "";
|
|
1410
|
+
const escapedLabel = escapeTs(field.label);
|
|
1411
|
+
return ` <div>
|
|
1412
|
+
<label htmlFor="${field.key}">${escapedLabel}</label>
|
|
1413
|
+
<input
|
|
1414
|
+
id="${field.key}"
|
|
1415
|
+
type="${inputType}"
|
|
1416
|
+
{...register("${field.key}")}${required}
|
|
1417
|
+
/>
|
|
1418
|
+
{errors.${field.key} && (
|
|
1419
|
+
<p role="alert">{errors.${field.key}?.message}</p>
|
|
1420
|
+
)}
|
|
1421
|
+
</div>`;
|
|
1422
|
+
}).join("\n");
|
|
1423
|
+
return `"use client"
|
|
1424
|
+
|
|
1425
|
+
import { useForm } from "react-hook-form"
|
|
1426
|
+
import { zodResolver } from "@hookform/resolvers/zod"
|
|
1427
|
+
import { ${camel}Schema, type ${pascal}FormData } from "@/cmx/generated"
|
|
1428
|
+
import { useState } from "react"
|
|
897
1429
|
|
|
898
|
-
|
|
899
|
-
|
|
1430
|
+
type Props = {
|
|
1431
|
+
action: (data: Record<string, unknown>) => Promise<{ success: true; id: string }>
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
export function ${pascal}Form({ action }: Props) {
|
|
1435
|
+
const [status, setStatus] = useState<"idle" | "submitting" | "success" | "error">("idle")
|
|
1436
|
+
|
|
1437
|
+
const {
|
|
1438
|
+
register,
|
|
1439
|
+
handleSubmit,
|
|
1440
|
+
reset,
|
|
1441
|
+
formState: { errors },
|
|
1442
|
+
} = useForm<${pascal}FormData>({
|
|
1443
|
+
resolver: zodResolver(${camel}Schema),
|
|
1444
|
+
})
|
|
1445
|
+
|
|
1446
|
+
const onSubmit = async (data: ${pascal}FormData) => {
|
|
1447
|
+
setStatus("submitting")
|
|
1448
|
+
try {
|
|
1449
|
+
await action(data)
|
|
1450
|
+
setStatus("success")
|
|
1451
|
+
reset()
|
|
1452
|
+
} catch {
|
|
1453
|
+
setStatus("error")
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
if (status === "success") {
|
|
1458
|
+
return <p>\u9001\u4FE1\u304C\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002\u3042\u308A\u304C\u3068\u3046\u3054\u3056\u3044\u307E\u3059\u3002</p>
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
return (
|
|
1462
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
1463
|
+
{/* Honeypot field */}
|
|
1464
|
+
<div style={{ position: "absolute", left: "-9999px" }} aria-hidden="true">
|
|
1465
|
+
<input type="text" name="_hp" tabIndex={-1} autoComplete="off" />
|
|
1466
|
+
</div>
|
|
1467
|
+
|
|
1468
|
+
${inputFields}
|
|
1469
|
+
|
|
1470
|
+
{status === "error" && (
|
|
1471
|
+
<p role="alert">\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002</p>
|
|
1472
|
+
)}
|
|
1473
|
+
|
|
1474
|
+
<button type="submit" disabled={status === "submitting"}>
|
|
1475
|
+
{status === "submitting" ? "\u9001\u4FE1\u4E2D..." : "\u9001\u4FE1"}
|
|
1476
|
+
</button>
|
|
1477
|
+
</form>
|
|
1478
|
+
)
|
|
1479
|
+
}
|
|
900
1480
|
`;
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1481
|
+
}
|
|
1482
|
+
function getInputType(fieldType) {
|
|
1483
|
+
switch (fieldType) {
|
|
1484
|
+
case "email":
|
|
1485
|
+
return "email";
|
|
1486
|
+
case "url":
|
|
1487
|
+
return "url";
|
|
1488
|
+
case "number":
|
|
1489
|
+
return "number";
|
|
1490
|
+
case "boolean":
|
|
1491
|
+
return "checkbox";
|
|
1492
|
+
case "textarea":
|
|
1493
|
+
case "richtext":
|
|
1494
|
+
return "text";
|
|
1495
|
+
default:
|
|
1496
|
+
return "text";
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// src/codegen/scaffold-seo.ts
|
|
1501
|
+
function scaffoldSitemap(collections, siteUrl) {
|
|
1502
|
+
const imports = collections.map((col) => {
|
|
1503
|
+
const safeSlug = sanitizeIdentifier(col.slug);
|
|
1504
|
+
const pascal = slugToPascalCase(safeSlug);
|
|
1505
|
+
return `import { get${pascal}Contents } from "@/cmx/generated"`;
|
|
1506
|
+
}).join("\n");
|
|
1507
|
+
const fetchBlocks = collections.map((col) => {
|
|
1508
|
+
const safeSlug = sanitizeIdentifier(col.slug);
|
|
1509
|
+
const pascal = slugToPascalCase(safeSlug);
|
|
1510
|
+
return ` const { contents: ${safeSlug.replace(/-/g, "")}Contents } = await get${pascal}Contents()
|
|
1511
|
+
for (const item of ${safeSlug.replace(/-/g, "")}Contents) {
|
|
1512
|
+
urls.push({
|
|
1513
|
+
url: \`\${baseUrl}/${col.slug}/\${item.slug}\`,
|
|
1514
|
+
lastModified: new Date(),
|
|
1515
|
+
})
|
|
1516
|
+
}`;
|
|
1517
|
+
}).join("\n\n");
|
|
1518
|
+
return `import type { MetadataRoute } from "next"
|
|
1519
|
+
${imports}
|
|
1520
|
+
|
|
1521
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "${siteUrl}"
|
|
1522
|
+
|
|
1523
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
1524
|
+
const urls: MetadataRoute.Sitemap = [
|
|
1525
|
+
{ url: baseUrl, lastModified: new Date() },
|
|
1526
|
+
]
|
|
1527
|
+
|
|
1528
|
+
${fetchBlocks}
|
|
1529
|
+
|
|
1530
|
+
return urls
|
|
1531
|
+
}
|
|
1532
|
+
`;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
// src/codegen/scaffolder.ts
|
|
1536
|
+
async function writeScaffoldFile(filePath, content, result, options) {
|
|
1537
|
+
const resolvedPath = resolve(filePath);
|
|
1538
|
+
const resolvedAppDir = resolve(options.appDir);
|
|
1539
|
+
if (!resolvedPath.startsWith(resolvedAppDir + "/") && resolvedPath !== resolvedAppDir) {
|
|
1540
|
+
throw new Error(`Path traversal detected: "${filePath}" resolves outside of appDir "${options.appDir}"`);
|
|
1541
|
+
}
|
|
1542
|
+
if (!options.force && existsSync2(filePath)) {
|
|
1543
|
+
result.skipped.push(filePath);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
if (options.dryRun) {
|
|
1547
|
+
result.created.push(filePath);
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
await mkdir2(dirname(filePath), { recursive: true });
|
|
1551
|
+
await writeFile2(filePath, content, "utf-8");
|
|
1552
|
+
result.created.push(filePath);
|
|
1553
|
+
}
|
|
1554
|
+
function parseOnly(only) {
|
|
1555
|
+
const [category, slug] = only.split(":");
|
|
1556
|
+
if (!category || !slug) return null;
|
|
1557
|
+
return { category, slug };
|
|
1558
|
+
}
|
|
1559
|
+
async function scaffold(options) {
|
|
1560
|
+
const { apiUrl, apiKey, appDir, dryRun, force, only } = options;
|
|
1561
|
+
console.log(`Fetching schema from ${apiUrl} ...`);
|
|
1562
|
+
const schema = await fetchSchema(apiUrl, apiKey);
|
|
1563
|
+
const forms = schema.forms ?? [];
|
|
1564
|
+
const filter = only ? parseOnly(only) : null;
|
|
1565
|
+
if (only && !filter) {
|
|
1566
|
+
console.error(`Invalid --only format: "${only}". Use category:slug (e.g. collections:blog)`);
|
|
1567
|
+
process.exit(1);
|
|
1568
|
+
}
|
|
1569
|
+
const result = { created: [], skipped: [] };
|
|
1570
|
+
const prefix = dryRun ? "[dry-run] " : "";
|
|
1571
|
+
if (!filter || filter.category === "collections") {
|
|
1572
|
+
const collections = filter ? schema.collections.filter((c) => c.slug === filter.slug) : schema.collections;
|
|
1573
|
+
for (const col of collections) {
|
|
1574
|
+
const safeSlug = sanitizeIdentifier(col.slug);
|
|
1575
|
+
if (!safeSlug) continue;
|
|
1576
|
+
const listPath = join3(appDir, safeSlug, "page.tsx");
|
|
1577
|
+
await writeScaffoldFile(listPath, scaffoldCollectionListPage(col), result, { dryRun, force, appDir });
|
|
1578
|
+
const detailPath = join3(appDir, safeSlug, "[slug]", "page.tsx");
|
|
1579
|
+
await writeScaffoldFile(detailPath, scaffoldCollectionDetailPage(col), result, { dryRun, force, appDir });
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
if (!filter || filter.category === "data-types") {
|
|
1583
|
+
const dataTypes = filter ? schema.dataTypes.filter((dt) => dt.slug === filter.slug) : schema.dataTypes;
|
|
1584
|
+
for (const dt of dataTypes) {
|
|
1585
|
+
const safeSlug = sanitizeIdentifier(dt.slug);
|
|
1586
|
+
if (!safeSlug) continue;
|
|
1587
|
+
const pagePath = join3(appDir, safeSlug, "page.tsx");
|
|
1588
|
+
await writeScaffoldFile(pagePath, scaffoldDataTypePage(dt), result, { dryRun, force, appDir });
|
|
909
1589
|
}
|
|
1590
|
+
}
|
|
1591
|
+
if (!filter || filter.category === "forms") {
|
|
1592
|
+
const filteredForms = filter ? forms.filter((f) => f.slug === filter.slug) : forms;
|
|
1593
|
+
for (const form of filteredForms) {
|
|
1594
|
+
const safeSlug = sanitizeIdentifier(form.slug);
|
|
1595
|
+
if (!safeSlug) continue;
|
|
1596
|
+
const pagePath = join3(appDir, safeSlug, "page.tsx");
|
|
1597
|
+
await writeScaffoldFile(pagePath, scaffoldFormPage(form), result, { dryRun, force, appDir });
|
|
1598
|
+
const componentPath = join3(appDir, safeSlug, "_components", `${safeSlug}-form.tsx`);
|
|
1599
|
+
await writeScaffoldFile(componentPath, scaffoldFormComponent(form), result, { dryRun, force, appDir });
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
if (!filter) {
|
|
1603
|
+
const sitemapPath = join3(appDir, "sitemap.ts");
|
|
1604
|
+
if (schema.collections.length > 0) {
|
|
1605
|
+
const siteUrl = apiUrl.replace(/\/api\/.*$/, "");
|
|
1606
|
+
await writeScaffoldFile(sitemapPath, scaffoldSitemap(schema.collections, siteUrl), result, { dryRun, force, appDir });
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
console.log("");
|
|
1610
|
+
if (result.created.length > 0) {
|
|
1611
|
+
console.log(`${prefix}Created ${result.created.length} file(s):`);
|
|
1612
|
+
for (const f of result.created) {
|
|
1613
|
+
console.log(` ${prefix}+ ${f}`);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
if (result.skipped.length > 0) {
|
|
1617
|
+
console.log(`
|
|
1618
|
+
Skipped ${result.skipped.length} file(s) (already exist):`);
|
|
1619
|
+
for (const f of result.skipped) {
|
|
1620
|
+
console.log(` - ${f}`);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
if (result.created.length === 0 && result.skipped.length === 0) {
|
|
1624
|
+
console.log("No files to scaffold.");
|
|
1625
|
+
}
|
|
1626
|
+
console.log("");
|
|
1627
|
+
console.log("Next steps:");
|
|
1628
|
+
console.log(" 1. Customize the generated pages' design (HTML/CSS)");
|
|
1629
|
+
console.log(" 2. Add navigation links to your layout");
|
|
1630
|
+
console.log(" 3. Run `npm run dev` to preview your site");
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// src/commands/create-data-entry.ts
|
|
1634
|
+
async function createDataEntry(options) {
|
|
1635
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
1636
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
1637
|
+
if (!apiUrl || !apiKey) {
|
|
1638
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
1639
|
+
process.exit(1);
|
|
1640
|
+
}
|
|
1641
|
+
if (!options.typeSlug) {
|
|
1642
|
+
console.error("Error: --type-slug is required");
|
|
910
1643
|
process.exit(1);
|
|
911
1644
|
}
|
|
1645
|
+
const input = readJsonInput(options);
|
|
1646
|
+
if (!input.dataJson) {
|
|
1647
|
+
console.error("Error: JSON must include dataJson");
|
|
1648
|
+
process.exit(1);
|
|
1649
|
+
}
|
|
1650
|
+
console.log(`Creating data entry for type: ${options.typeSlug}...`);
|
|
1651
|
+
const result = await manageApiFetch(apiUrl, apiKey, "POST", `/data/${options.typeSlug}`, input);
|
|
1652
|
+
console.log("Data entry created successfully:");
|
|
1653
|
+
console.log(JSON.stringify(result, null, 2));
|
|
912
1654
|
}
|
|
913
1655
|
|
|
914
1656
|
// src/cli.ts
|
|
@@ -922,12 +1664,46 @@ program.command("list-components").description("List all custom components").act
|
|
|
922
1664
|
program.command("create-collection").description("Create a new collection").option("--json <json>", "JSON string with collection data").option("--type <type>", "Collection type (post, page, doc, news)").option("--slug <slug>", "Collection slug").option("--name <name>", "Collection name").option("--description <description>", "Collection description").action(createCollection);
|
|
923
1665
|
program.command("create-data-type").description("Create a new data type").option("--json <json>", "JSON string with data type data (recommended)").option("--slug <slug>", "Data type slug").option("--name <name>", "Data type name").option("--description <description>", "Data type description").action(createDataType);
|
|
924
1666
|
program.command("create-form").description("Create a new form definition").option("--json <json>", "JSON string with form data (recommended)").option("--slug <slug>", "Form slug").option("--name <name>", "Form name").option("--description <description>", "Form description").action(createForm);
|
|
925
|
-
program.command("sync-components").description("Sync custom components").option("--file <file>", "Path to JSON file with components").option("--json <json>", "JSON string with components data").action(syncComponents);
|
|
926
|
-
program.command("generate").description("Generate TypeScript types and functions from CMX schema").option("--output <dir>", "Output directory (default: cmx/generated)").action(
|
|
1667
|
+
program.command("sync-components").description("Sync custom components").option("--file <file>", "Path to JSON file with components").option("--json <json>", "JSON string with components data").option("--environment <env>", "Deployment environment (auto-detected if omitted)").action(syncComponents);
|
|
1668
|
+
program.command("generate").description("Generate TypeScript types and functions from CMX schema").option("--output <dir>", "Output directory (default: cmx/generated)").action((options) => {
|
|
1669
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
1670
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
1671
|
+
if (!apiUrl || !apiKey) {
|
|
1672
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
1673
|
+
process.exit(1);
|
|
1674
|
+
}
|
|
1675
|
+
return generate({
|
|
1676
|
+
apiUrl,
|
|
1677
|
+
apiKey,
|
|
1678
|
+
outputDir: options.output || "cmx/generated"
|
|
1679
|
+
});
|
|
1680
|
+
});
|
|
927
1681
|
program.command("update-collection").description("Update an existing collection").requiredOption("--slug <slug>", "Current slug of the collection").option("--json <json>", "JSON string with update data").option("--new-slug <slug>", "New slug").option("--name <name>", "New name").option("--description <description>", "New description").action(updateCollection);
|
|
928
1682
|
program.command("delete-collection").description("Delete a collection").requiredOption("--slug <slug>", "Collection slug").option("--force", "Skip confirmation prompt").option("--cascade", "Also delete associated content").action(deleteCollection);
|
|
929
1683
|
program.command("update-data-type").description("Update an existing data type").requiredOption("--slug <slug>", "Current slug of the data type").option("--json <json>", "JSON string with update data").option("--new-slug <slug>", "New slug").option("--name <name>", "New name").option("--description <description>", "New description").action(updateDataType);
|
|
930
1684
|
program.command("delete-data-type").description("Delete a data type").requiredOption("--slug <slug>", "Data type slug").option("--force", "Skip confirmation prompt").option("--cascade", "Also delete associated data entries").action(deleteDataType);
|
|
931
1685
|
program.command("update-form").description("Update an existing form definition").requiredOption("--slug <slug>", "Current slug of the form").option("--json <json>", "JSON string with update data").option("--new-slug <slug>", "New slug").option("--name <name>", "New name").option("--description <description>", "New description").action(updateForm);
|
|
932
1686
|
program.command("delete-form").description("Delete a form definition").requiredOption("--slug <slug>", "Form slug").option("--force", "Skip confirmation prompt").action(deleteForm);
|
|
1687
|
+
program.command("list-collection-data-types").description("List data types attached to a collection").requiredOption("--collection <slug>", "Collection slug").action(listCollectionDataTypes);
|
|
1688
|
+
program.command("add-collection-data-type").description("Add a data type to a collection (from preset or custom)").requiredOption("--collection <slug>", "Collection slug").option("--preset <slug>", "Preset slug (e.g. categories, tags, authors)").option("--json <json>", "JSON string with custom data type definition").option("--file <file>", "Path to JSON file with custom data type definition").action(addCollectionDataType);
|
|
1689
|
+
program.command("remove-collection-data-type").description("Remove a data type from a collection").requiredOption("--collection <slug>", "Collection slug").requiredOption("--data-type <slug>", "Data type slug to remove").action(removeCollectionDataType);
|
|
1690
|
+
program.command("link-collection-data-type").description("Link an existing global data type to a collection").requiredOption("--collection <slug>", "Collection slug").requiredOption("--data-type <slug>", "Global data type slug to link").requiredOption("--field-slug <slug>", "Reference field slug").requiredOption("--label <label>", "Reference field display label").option("--reference-type <type>", "Reference type: single or multiple (default: single)", "single").action(linkCollectionDataType);
|
|
1691
|
+
program.command("list-collection-presets").description("List available presets for collection data types").option("--type <type>", "Collection type (post, news, doc, page)").action(listCollectionPresets);
|
|
1692
|
+
program.command("scaffold").description("Generate Next.js pages from CMX schema").option("--app-dir <dir>", "App directory path (default: app/(public))").option("--dry-run", "Preview without writing files").option("--force", "Overwrite existing files").option("--only <filter>", "Scaffold only specific items (e.g. collections:blog)").action((options) => {
|
|
1693
|
+
const apiUrl = process.env.CMX_API_URL;
|
|
1694
|
+
const apiKey = process.env.CMX_API_KEY;
|
|
1695
|
+
if (!apiUrl || !apiKey) {
|
|
1696
|
+
console.error("Error: CMX_API_URL and CMX_API_KEY environment variables are required");
|
|
1697
|
+
process.exit(1);
|
|
1698
|
+
}
|
|
1699
|
+
return scaffold({
|
|
1700
|
+
apiUrl,
|
|
1701
|
+
apiKey,
|
|
1702
|
+
appDir: options.appDir || "app/(public)",
|
|
1703
|
+
dryRun: options.dryRun,
|
|
1704
|
+
force: options.force,
|
|
1705
|
+
only: options.only
|
|
1706
|
+
});
|
|
1707
|
+
});
|
|
1708
|
+
program.command("create-data-entry").description("Create a new data entry").requiredOption("--type-slug <slug>", "Data type slug").option("--json <json>", "JSON string with entry data").option("--file <file>", "Path to JSON file with entry data").action(createDataEntry);
|
|
933
1709
|
program.parse();
|