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 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/core/date-reviver.ts
9
- var isoDateFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
10
- function dateReviver(_key, value) {
11
- if (typeof value === "string" && isoDateFormat.test(value)) {
12
- return new Date(value);
13
- }
14
- return value;
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
- const body = [204, 205, 304].includes(res.status) ? null : await res.text();
215
- const data = body ? JSON.parse(body, dateReviver) : {};
216
- return { data, status: res.status, headers: res.headers };
217
- };
218
- var getPutManageFormDefinitionsSlugUrl = (slug) => {
219
- return `/api/v1/sdk/manage/form-definitions/${slug}`;
220
- };
221
- var putManageFormDefinitionsSlug = async (slug, updateFormDefinitionRequest, options) => {
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
- const body = [204, 205, 304].includes(res.status) ? null : await res.text();
234
- const data = body ? JSON.parse(body, dateReviver) : {};
235
- return { data, status: res.status, headers: res.headers };
236
- };
237
- var getDeleteManageFormDefinitionsSlugUrl = (slug) => {
238
- return `/api/v1/sdk/manage/form-definitions/${slug}`;
239
- };
240
- var deleteManageFormDefinitionsSlug = async (slug, options) => {
241
- const res = await fetch(
242
- getDeleteManageFormDefinitionsSlugUrl(slug),
243
- {
244
- ...options,
245
- method: "DELETE"
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
- const body = [204, 205, 304].includes(res.status) ? null : await res.text();
249
- const data = body ? JSON.parse(body, dateReviver) : {};
250
- return { data, status: res.status, headers: res.headers };
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 response = await getManageCollections();
258
- const collections = Array.isArray(response.data) ? response.data : [];
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 response = await getManageDataTypes();
285
- const dataTypes = Array.isArray(response.data) ? response.data : [];
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 response = await getManageFormDefinitions();
313
- const forms = Array.isArray(response.data) ? response.data : [];
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 response = await getManageComponents();
341
- const components = Array.isArray(response.data) ? response.data : [];
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/utils.ts
364
- import { readFileSync } from "fs";
365
- function readJsonInput(args) {
366
- const jsonIdx = args.indexOf("--json");
367
- const fileIdx = args.indexOf("--file");
368
- if (jsonIdx !== -1 && args[jsonIdx + 1]) {
369
- try {
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
- if (fileIdx !== -1 && args[fileIdx + 1]) {
377
- try {
378
- const content = readFileSync(args[fileIdx + 1], "utf-8");
379
- return JSON.parse(content);
380
- } catch (err) {
381
- console.error(`Error: Failed to read JSON file: ${args[fileIdx + 1]}`);
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 response = await postManageCollections(input);
214
+ const result = await manageApiFetch(apiUrl, apiKey, "POST", "/collections", input);
422
215
  console.log("Collection created successfully:");
423
- console.log(JSON.stringify(response.data, null, 2));
216
+ console.log(JSON.stringify(result, null, 2));
424
217
  }
425
218
 
426
219
  // src/commands/create-data-type.ts
427
- async function createDataType(args, apiUrl, apiKey) {
428
- const input = readJsonInput(args);
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 response = await postManageDataTypes(input);
233
+ const result = await manageApiFetch(apiUrl, apiKey, "POST", "/data-types", input);
435
234
  console.log("Data type created successfully:");
436
- console.log(JSON.stringify(response.data, null, 2));
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 response = await postManageFormDefinitions(data);
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(response.data, null, 2));
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 putManageCollectionsSlug(options.slug, data);
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 putManageDataTypesSlug(options.slug, data);
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 putManageFormDefinitionsSlug(options.slug, data);
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((resolve) => {
384
+ return new Promise((resolve2) => {
563
385
  rl.question(`${message} (y/N): `, (answer) => {
564
386
  rl.close();
565
- resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
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 deleteManageCollectionsSlug(options.slug);
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((resolve) => {
429
+ return new Promise((resolve2) => {
602
430
  rl.question(`${message} (y/N): `, (answer) => {
603
431
  rl.close();
604
- resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
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 deleteManageDataTypesSlug(options.slug);
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((resolve) => {
474
+ return new Promise((resolve2) => {
641
475
  rl.question(`${message} (y/N): `, (answer) => {
642
476
  rl.close();
643
- resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
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 deleteManageFormDefinitionsSlug(options.slug);
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(args, apiUrl, apiKey) {
735
- const environment = parseArg(args, "--environment") || detectEnvironment();
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/commands/generate.ts
761
- import * as fs from "fs";
762
- import * as path from "path";
763
- function mapFieldType(field) {
764
- const baseType = (() => {
765
- switch (field.type) {
766
- case "text":
767
- case "textarea":
768
- case "richtext":
769
- case "markdown":
770
- case "email":
771
- case "url":
772
- case "slug":
773
- return "string";
774
- case "number":
775
- return "number";
776
- case "boolean":
777
- case "checkbox":
778
- return "boolean";
779
- case "date":
780
- case "datetime":
781
- return "string";
782
- // ISO 8601 string
783
- case "select":
784
- case "radio":
785
- return "string";
786
- case "image":
787
- case "file":
788
- return "string";
789
- // URL
790
- case "relation":
791
- return "string";
792
- // ID or slug
793
- default:
794
- return "unknown";
795
- }
796
- })();
797
- return field.required ? baseType : `${baseType} | null`;
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 interfaceName = toPascalCase(dataType.slug);
801
- const fields = dataType.fields.map((field) => {
802
- const comment = field.description ? `/** ${field.label} - ${field.description} */` : `/** ${field.label} */`;
803
- return ` ${comment}
804
- ${field.key}: ${mapFieldType(field)}`;
805
- }).join("\n");
806
- return `// Auto-generated by cmx-sdk generate
807
- // Data Type: ${dataType.name} (${dataType.slug})
808
- // Do not edit manually.
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
- import { getDataEntries, getDataEntry } from "cmx-sdk"
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
- export interface ${interfaceName} {
813
- ${fields}
814
- _meta: {
815
- sortOrder: number
816
- createdAt: string
817
- updatedAt: string
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
- export async function get${interfaceName}(options?: {
822
- sortBy?: string
823
- sortOrder?: string
824
- limit?: number
825
- offset?: number
826
- }): Promise<{ type: string; items: ${interfaceName}[]; count: number }> {
827
- const result = await getDataEntries("${dataType.slug}", options)
828
- return result as unknown as { type: string; items: ${interfaceName}[]; count: number }
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
- export async function get${interfaceName}ById(id: string): Promise<${interfaceName}> {
832
- const result = await getDataEntry("${dataType.slug}", id)
833
- return result as unknown as ${interfaceName}
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 generateCollectionFile(collection) {
838
- const functionPrefix = toPascalCase(collection.slug);
839
- return `// Auto-generated by cmx-sdk generate
840
- // Collection: ${collection.name} (${collection.slug})
841
- // Do not edit manually.
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
- import { getCollectionContents, getCollectionContentDetail } from "cmx-sdk"
844
- import type { CollectionContentsResponse, CollectionContentDetailResponse } from "cmx-sdk"
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 get${functionPrefix}Contents(): Promise<CollectionContentsResponse> {
847
- return getCollectionContents("${collection.slug}")
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 get${functionPrefix}ContentDetail(slug: string): Promise<CollectionContentDetailResponse> {
851
- return getCollectionContentDetail("${collection.slug}", slug)
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
- function toPascalCase(str) {
856
- return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
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
- fs.writeFileSync(path.join(collectionsDir, "index.ts"), collectionIndexContent, "utf-8");
881
- console.log(`\u2713 Generated ${path.join(collectionsDir, "index.ts")}`);
882
- for (const dataType of schema.dataTypes) {
883
- const filePath = path.join(dataTypesDir, `${dataType.slug}.ts`);
884
- const content = generateDataTypeFile(dataType);
885
- fs.writeFileSync(filePath, content, "utf-8");
886
- console.log(`\u2713 Generated ${filePath}`);
887
- }
888
- const dataTypesIndexContent = `// Auto-generated by cmx-sdk generate
889
- // Do not edit manually.
890
-
891
- ${schema.dataTypes.map((dt) => `export * from "./${dt.slug}"`).join("\n")}
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
- fs.writeFileSync(path.join(dataTypesDir, "index.ts"), dataTypesIndexContent, "utf-8");
894
- console.log(`\u2713 Generated ${path.join(dataTypesDir, "index.ts")}`);
895
- const mainIndexContent = `// Auto-generated by cmx-sdk generate
896
- // Do not edit manually.
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
- export * from "./data-types"
899
- export * from "./collections"
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
- fs.writeFileSync(path.join(outputDir, "index.ts"), mainIndexContent, "utf-8");
902
- console.log(`\u2713 Generated ${path.join(outputDir, "index.ts")}`);
903
- console.log("\n\u2728 Generation complete!");
904
- console.log(`Output directory: ${outputDir}`);
905
- } catch (error) {
906
- console.error("Error generating types:", error);
907
- if (error instanceof Error) {
908
- console.error(error.message);
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(generate);
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();