strapi2front 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -134
- package/dist/bin/strapi2front.js +196 -232
- package/dist/bin/strapi2front.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +192 -228
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/bin/strapi2front.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
3
|
+
import pc3 from 'picocolors';
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import fs5 from 'fs/promises';
|
|
6
6
|
import path6 from 'path';
|
|
7
7
|
import { spawn, execSync } from 'child_process';
|
|
8
8
|
import fs6 from 'fs';
|
|
9
9
|
import { loadConfig, detectStrapiVersion, fetchSchema, parseSchema } from '@strapi2front/core';
|
|
10
|
-
import { generateByFeature
|
|
10
|
+
import { generateByFeature } from '@strapi2front/generators';
|
|
11
11
|
|
|
12
12
|
var FRAMEWORK_DETECTORS = {
|
|
13
13
|
astro: {
|
|
@@ -174,12 +174,12 @@ function getMajorVersion(version) {
|
|
|
174
174
|
return match ? parseInt(match[1], 10) : null;
|
|
175
175
|
}
|
|
176
176
|
async function runInitPrompts(detection) {
|
|
177
|
-
p.intro(
|
|
177
|
+
p.intro(pc3.cyan("strapi2front setup"));
|
|
178
178
|
p.note(
|
|
179
179
|
[
|
|
180
|
-
`Framework: ${
|
|
181
|
-
`TypeScript: ${detection.typescript.enabled ?
|
|
182
|
-
`Package Manager: ${
|
|
180
|
+
`Framework: ${pc3.green(getFrameworkDisplayName(detection.framework.name))} ${detection.framework.version ? pc3.dim(`v${detection.framework.version}`) : ""}`,
|
|
181
|
+
`TypeScript: ${detection.typescript.enabled ? pc3.green("enabled") : pc3.yellow("disabled")}`,
|
|
182
|
+
`Package Manager: ${pc3.green(detection.packageManager.name)}`
|
|
183
183
|
].join("\n"),
|
|
184
184
|
"Detected Configuration"
|
|
185
185
|
);
|
|
@@ -191,13 +191,13 @@ async function runInitPrompts(detection) {
|
|
|
191
191
|
} else {
|
|
192
192
|
const majorVersion = getMajorVersion(detection.framework.version);
|
|
193
193
|
if (majorVersion !== null && majorVersion < 4) {
|
|
194
|
-
p.log.warn(
|
|
194
|
+
p.log.warn(pc3.yellow(`Astro v${majorVersion} detected. Upgrade to v4+ to enable Actions.`));
|
|
195
195
|
canGenerateActions = false;
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
let outputFormat = "typescript";
|
|
199
199
|
if (!detection.typescript.enabled) {
|
|
200
|
-
p.log.info(
|
|
200
|
+
p.log.info(pc3.dim("TypeScript not detected. Files will be generated as JavaScript with JSDoc annotations."));
|
|
201
201
|
outputFormat = "jsdoc";
|
|
202
202
|
}
|
|
203
203
|
const defaultUrl = "http://localhost:1337";
|
|
@@ -221,7 +221,7 @@ async function runInitPrompts(detection) {
|
|
|
221
221
|
}
|
|
222
222
|
const strapiUrl = (strapiUrlInput || "").trim() || defaultUrl;
|
|
223
223
|
p.log.message(
|
|
224
|
-
|
|
224
|
+
pc3.dim(
|
|
225
225
|
`
|
|
226
226
|
To generate a token: Strapi Admin > Settings > API Tokens > Create new API Token
|
|
227
227
|
Required permissions:
|
|
@@ -234,8 +234,8 @@ async function runInitPrompts(detection) {
|
|
|
234
234
|
)
|
|
235
235
|
);
|
|
236
236
|
const strapiToken = await p.text({
|
|
237
|
-
message: "What is your Strapi
|
|
238
|
-
placeholder: "Press Enter to skip (you can add it later in .env)"
|
|
237
|
+
message: "What is your Strapi sync token?",
|
|
238
|
+
placeholder: "Press Enter to skip (you can add it later in .env as STRAPI_SYNC_TOKEN)"
|
|
239
239
|
});
|
|
240
240
|
if (p.isCancel(strapiToken)) {
|
|
241
241
|
p.cancel("Setup cancelled");
|
|
@@ -243,7 +243,7 @@ async function runInitPrompts(detection) {
|
|
|
243
243
|
}
|
|
244
244
|
const trimmedToken = (strapiToken || "").trim();
|
|
245
245
|
if (trimmedToken === "") {
|
|
246
|
-
p.log.info(
|
|
246
|
+
p.log.info(pc3.dim("Token skipped. Remember to add STRAPI_SYNC_TOKEN to your .env file later."));
|
|
247
247
|
}
|
|
248
248
|
const strapiVersion = await p.select({
|
|
249
249
|
message: "What version of Strapi are you using?",
|
|
@@ -257,7 +257,7 @@ async function runInitPrompts(detection) {
|
|
|
257
257
|
p.cancel("Setup cancelled");
|
|
258
258
|
return null;
|
|
259
259
|
}
|
|
260
|
-
p.log.info(
|
|
260
|
+
p.log.info(pc3.dim(`Using Strapi ${strapiVersion}. This can be changed later in strapi.config.ts`));
|
|
261
261
|
const defaultPrefix = "/api";
|
|
262
262
|
const apiPrefixInput = await p.text({
|
|
263
263
|
message: "What is your Strapi API prefix?",
|
|
@@ -277,7 +277,7 @@ async function runInitPrompts(detection) {
|
|
|
277
277
|
}
|
|
278
278
|
const apiPrefix = (apiPrefixInput || "").trim() || defaultPrefix;
|
|
279
279
|
if (apiPrefix !== defaultPrefix) {
|
|
280
|
-
p.log.info(
|
|
280
|
+
p.log.info(pc3.dim(`Using custom API prefix: ${apiPrefix}`));
|
|
281
281
|
}
|
|
282
282
|
const outputDir = await p.text({
|
|
283
283
|
message: "Where should we generate the Strapi files?",
|
|
@@ -301,10 +301,24 @@ async function runInitPrompts(detection) {
|
|
|
301
301
|
hint: isTypeScript ? "Typed service functions for data fetching" : "Service functions with JSDoc annotations"
|
|
302
302
|
}
|
|
303
303
|
];
|
|
304
|
+
if (isTypeScript) {
|
|
305
|
+
featureOptions.push({
|
|
306
|
+
value: "schemas",
|
|
307
|
+
label: "Schemas",
|
|
308
|
+
hint: "Zod validation schemas for forms (React Hook Form, TanStack Form, etc.)"
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
featureOptions.push({
|
|
312
|
+
value: "upload",
|
|
313
|
+
label: "Upload",
|
|
314
|
+
hint: "File upload helpers (action + public client for browser uploads)"
|
|
315
|
+
});
|
|
304
316
|
if (canGenerateActions && isTypeScript) {
|
|
305
317
|
featureOptions.push({ value: "actions", label: "Astro Actions", hint: "Type-safe actions for client/server" });
|
|
306
318
|
}
|
|
307
|
-
const initialFeatures =
|
|
319
|
+
const initialFeatures = ["types", "services"];
|
|
320
|
+
if (isTypeScript) initialFeatures.push("schemas");
|
|
321
|
+
if (canGenerateActions && isTypeScript) initialFeatures.push("actions");
|
|
308
322
|
const features = await p.multiselect({
|
|
309
323
|
message: "What would you like to generate?",
|
|
310
324
|
options: featureOptions,
|
|
@@ -315,6 +329,19 @@ async function runInitPrompts(detection) {
|
|
|
315
329
|
p.cancel("Setup cancelled");
|
|
316
330
|
return null;
|
|
317
331
|
}
|
|
332
|
+
const selectedFeatures = new Set(features);
|
|
333
|
+
if (selectedFeatures.has("services") && !selectedFeatures.has("types")) {
|
|
334
|
+
selectedFeatures.add("types");
|
|
335
|
+
p.log.info(pc3.dim("Auto-enabled Types (required by Services)"));
|
|
336
|
+
}
|
|
337
|
+
if (selectedFeatures.has("actions") && !selectedFeatures.has("services")) {
|
|
338
|
+
selectedFeatures.add("services");
|
|
339
|
+
p.log.info(pc3.dim("Auto-enabled Services (required by Actions)"));
|
|
340
|
+
if (!selectedFeatures.has("types")) {
|
|
341
|
+
selectedFeatures.add("types");
|
|
342
|
+
p.log.info(pc3.dim("Auto-enabled Types (required by Services)"));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
318
345
|
return {
|
|
319
346
|
strapiUrl,
|
|
320
347
|
strapiToken: trimmedToken,
|
|
@@ -322,28 +349,31 @@ async function runInitPrompts(detection) {
|
|
|
322
349
|
apiPrefix,
|
|
323
350
|
outputFormat,
|
|
324
351
|
outputDir: (outputDir || "").trim() || "src/strapi",
|
|
325
|
-
|
|
326
|
-
generateServices:
|
|
352
|
+
generateTypes: selectedFeatures.has("types"),
|
|
353
|
+
generateServices: selectedFeatures.has("services"),
|
|
354
|
+
generateActions: canGenerateActions && isTypeScript && selectedFeatures.has("actions"),
|
|
355
|
+
generateSchemas: selectedFeatures.has("schemas"),
|
|
356
|
+
generateUpload: selectedFeatures.has("upload")
|
|
327
357
|
};
|
|
328
358
|
}
|
|
329
359
|
var logger = {
|
|
330
360
|
info: (message) => {
|
|
331
|
-
console.log(
|
|
361
|
+
console.log(pc3.blue("i"), message);
|
|
332
362
|
},
|
|
333
363
|
success: (message) => {
|
|
334
|
-
console.log(
|
|
364
|
+
console.log(pc3.green("v"), message);
|
|
335
365
|
},
|
|
336
366
|
warn: (message) => {
|
|
337
|
-
console.log(
|
|
367
|
+
console.log(pc3.yellow("!"), message);
|
|
338
368
|
},
|
|
339
369
|
error: (message) => {
|
|
340
|
-
console.log(
|
|
370
|
+
console.log(pc3.red("x"), message);
|
|
341
371
|
},
|
|
342
372
|
step: (message) => {
|
|
343
|
-
console.log(
|
|
373
|
+
console.log(pc3.cyan(">"), message);
|
|
344
374
|
},
|
|
345
375
|
dim: (message) => {
|
|
346
|
-
console.log(
|
|
376
|
+
console.log(pc3.dim(message));
|
|
347
377
|
},
|
|
348
378
|
newLine: () => {
|
|
349
379
|
console.log("");
|
|
@@ -403,22 +433,35 @@ async function initCommand(_options) {
|
|
|
403
433
|
apiPrefix: answers.apiPrefix,
|
|
404
434
|
outputFormat: answers.outputFormat,
|
|
405
435
|
outputDir: answers.outputDir,
|
|
406
|
-
|
|
436
|
+
generateTypes: answers.generateTypes,
|
|
407
437
|
generateServices: answers.generateServices,
|
|
438
|
+
generateActions: answers.generateActions,
|
|
439
|
+
generateSchemas: answers.generateSchemas,
|
|
440
|
+
generateUpload: answers.generateUpload,
|
|
408
441
|
moduleType
|
|
409
442
|
});
|
|
410
443
|
const configPath = path6.join(cwd, `strapi.config.${configExtension}`);
|
|
411
444
|
await fs5.writeFile(configPath, configContent, "utf-8");
|
|
412
445
|
const envPath = path6.join(cwd, ".env");
|
|
413
|
-
|
|
446
|
+
const envVars = {
|
|
414
447
|
STRAPI_URL: answers.strapiUrl,
|
|
415
|
-
|
|
416
|
-
|
|
448
|
+
STRAPI_SYNC_TOKEN: answers.strapiToken,
|
|
449
|
+
// Token for syncing schema (dev only)
|
|
450
|
+
STRAPI_TOKEN: ""
|
|
451
|
+
// Token for frontend API calls (production)
|
|
452
|
+
};
|
|
453
|
+
if (answers.generateUpload) {
|
|
454
|
+
envVars.PUBLIC_STRAPI_URL = answers.strapiUrl;
|
|
455
|
+
envVars.PUBLIC_STRAPI_UPLOAD_TOKEN = "";
|
|
456
|
+
}
|
|
457
|
+
await appendToEnvFile(envPath, envVars, answers.generateUpload);
|
|
458
|
+
const envExamplePath = path6.join(cwd, ".env.example");
|
|
459
|
+
await createEnvExampleFile(envExamplePath, answers.generateUpload);
|
|
417
460
|
const outputPath = path6.join(cwd, answers.outputDir);
|
|
418
461
|
await fs5.mkdir(outputPath, { recursive: true });
|
|
419
462
|
s.stop("Configuration files created");
|
|
420
463
|
const installDeps = await p.confirm({
|
|
421
|
-
message: "Install required dependencies (strapi2front, strapi
|
|
464
|
+
message: "Install required dependencies (strapi2front, @strapi/client)?",
|
|
422
465
|
initialValue: true
|
|
423
466
|
});
|
|
424
467
|
if (p.isCancel(installDeps)) {
|
|
@@ -427,49 +470,52 @@ async function initCommand(_options) {
|
|
|
427
470
|
}
|
|
428
471
|
if (installDeps) {
|
|
429
472
|
const installStrapi2frontCmd = getInstallDevCommand(packageManager.name, "strapi2front");
|
|
430
|
-
s.start(`Installing strapi2front... (${
|
|
473
|
+
s.start(`Installing strapi2front... (${pc3.dim(installStrapi2frontCmd)})`);
|
|
431
474
|
try {
|
|
432
475
|
await execAsync(installStrapi2frontCmd, cwd);
|
|
433
|
-
s.stop(`${
|
|
476
|
+
s.stop(`${pc3.green("\u2713")} strapi2front installed`);
|
|
434
477
|
} catch {
|
|
435
|
-
s.stop(`${
|
|
478
|
+
s.stop(`${pc3.red("\u2717")} Failed to install strapi2front`);
|
|
436
479
|
logger.warn(`Please install manually: ${installStrapi2frontCmd}`);
|
|
437
480
|
}
|
|
438
|
-
const installSdkCmd = getInstallCommand(packageManager.name, "strapi
|
|
439
|
-
s.start(`Installing strapi
|
|
481
|
+
const installSdkCmd = getInstallCommand(packageManager.name, "@strapi/client");
|
|
482
|
+
s.start(`Installing @strapi/client... (${pc3.dim(installSdkCmd)})`);
|
|
440
483
|
try {
|
|
441
484
|
await execAsync(installSdkCmd, cwd);
|
|
442
|
-
s.stop(`${
|
|
485
|
+
s.stop(`${pc3.green("\u2713")} @strapi/client installed`);
|
|
443
486
|
} catch {
|
|
444
|
-
s.stop(`${
|
|
487
|
+
s.stop(`${pc3.red("\u2717")} Failed to install @strapi/client`);
|
|
445
488
|
logger.warn(`Please install manually: ${installSdkCmd}`);
|
|
446
489
|
}
|
|
447
490
|
} else {
|
|
448
|
-
p.log.info(
|
|
449
|
-
p.log.info(
|
|
450
|
-
p.log.info(
|
|
491
|
+
p.log.info(pc3.dim("Remember to install dependencies manually:"));
|
|
492
|
+
p.log.info(pc3.dim(` ${getInstallDevCommand(packageManager.name, "strapi2front")}`));
|
|
493
|
+
p.log.info(pc3.dim(` ${getInstallCommand(packageManager.name, "@strapi/client")}`));
|
|
451
494
|
}
|
|
452
495
|
const configFileName = `strapi.config.${configExtension}`;
|
|
453
496
|
const fileExt = answers.outputFormat === "jsdoc" ? ".js" : ".ts";
|
|
454
497
|
p.note(
|
|
455
498
|
[
|
|
456
|
-
`${
|
|
457
|
-
`${
|
|
458
|
-
`${
|
|
499
|
+
`${pc3.green("\u2713")} Created ${pc3.cyan(configFileName)}`,
|
|
500
|
+
`${pc3.green("\u2713")} Updated ${pc3.cyan(".env")} with Strapi tokens`,
|
|
501
|
+
`${pc3.green("\u2713")} Created ${pc3.cyan(".env.example")} for reference`,
|
|
502
|
+
`${pc3.green("\u2713")} Created output directory ${pc3.cyan(answers.outputDir)}`,
|
|
459
503
|
"",
|
|
460
|
-
`Output format: ${
|
|
504
|
+
`Output format: ${pc3.cyan(answers.outputFormat === "jsdoc" ? "JavaScript (JSDoc)" : "TypeScript")}`,
|
|
461
505
|
"",
|
|
462
506
|
`Next steps:`,
|
|
463
|
-
` 1. Run ${
|
|
464
|
-
` 2. Import from ${
|
|
465
|
-
|
|
507
|
+
` 1. Run ${pc3.cyan("npx strapi2front sync")} to generate files`,
|
|
508
|
+
` 2. Import from ${pc3.cyan(answers.outputDir + "/collections/*" + fileExt)}`,
|
|
509
|
+
"",
|
|
510
|
+
`Docs: ${pc3.dim("https://strapi2front.dev/docs")}`
|
|
466
511
|
].join("\n"),
|
|
467
512
|
"Setup complete!"
|
|
468
513
|
);
|
|
469
|
-
p.outro(
|
|
514
|
+
p.outro(pc3.green("Happy coding!"));
|
|
470
515
|
} catch (error) {
|
|
471
516
|
s.stop("Failed to create configuration files");
|
|
472
517
|
logger.error(error instanceof Error ? error.message : "Unknown error");
|
|
518
|
+
logger.info(`Docs: ${pc3.dim("https://strapi2front.dev/docs/installation")}`);
|
|
473
519
|
process.exit(1);
|
|
474
520
|
}
|
|
475
521
|
}
|
|
@@ -482,7 +528,8 @@ function generateConfigFile(answers) {
|
|
|
482
528
|
export default defineConfig({
|
|
483
529
|
// Strapi connection
|
|
484
530
|
url: process.env.STRAPI_URL || "${answers.strapiUrl}",
|
|
485
|
-
|
|
531
|
+
// Token for syncing schema (uses STRAPI_SYNC_TOKEN with fallback to STRAPI_TOKEN)
|
|
532
|
+
token: process.env.STRAPI_SYNC_TOKEN || process.env.STRAPI_TOKEN,
|
|
486
533
|
|
|
487
534
|
// API prefix (default: "/api")
|
|
488
535
|
apiPrefix: "${answers.apiPrefix}",
|
|
@@ -493,17 +540,15 @@ export default defineConfig({
|
|
|
493
540
|
// Output configuration
|
|
494
541
|
output: {
|
|
495
542
|
path: "${answers.outputDir}",
|
|
496
|
-
types: "types",
|
|
497
|
-
services: "services",
|
|
498
|
-
actions: "actions/strapi",
|
|
499
|
-
structure: 'by-feature' // or 'by-layer'
|
|
500
543
|
},
|
|
501
544
|
|
|
502
545
|
// Features to generate
|
|
503
546
|
features: {
|
|
504
|
-
types:
|
|
547
|
+
types: ${answers.generateTypes},
|
|
505
548
|
services: ${answers.generateServices},
|
|
506
549
|
actions: ${answers.generateActions},
|
|
550
|
+
schemas: ${answers.generateSchemas}, // Zod schemas for form validation (React Hook Form, TanStack Form, etc.)
|
|
551
|
+
upload: ${answers.generateUpload}, // File upload helpers (action + public client)
|
|
507
552
|
},
|
|
508
553
|
|
|
509
554
|
// Strapi version
|
|
@@ -518,7 +563,8 @@ import { defineConfig } from "strapi2front";
|
|
|
518
563
|
export default defineConfig({
|
|
519
564
|
// Strapi connection
|
|
520
565
|
url: process.env.STRAPI_URL || "${answers.strapiUrl}",
|
|
521
|
-
|
|
566
|
+
// Token for syncing schema (uses STRAPI_SYNC_TOKEN with fallback to STRAPI_TOKEN)
|
|
567
|
+
token: process.env.STRAPI_SYNC_TOKEN || process.env.STRAPI_TOKEN,
|
|
522
568
|
|
|
523
569
|
// API prefix (default: "/api")
|
|
524
570
|
apiPrefix: "${answers.apiPrefix}",
|
|
@@ -532,16 +578,15 @@ export default defineConfig({
|
|
|
532
578
|
// Output configuration
|
|
533
579
|
output: {
|
|
534
580
|
path: "${answers.outputDir}",
|
|
535
|
-
types: "types",
|
|
536
|
-
services: "services",
|
|
537
|
-
structure: 'by-feature' // or 'by-layer'
|
|
538
581
|
},
|
|
539
582
|
|
|
540
583
|
// Features to generate
|
|
541
584
|
features: {
|
|
542
|
-
types:
|
|
585
|
+
types: ${answers.generateTypes},
|
|
543
586
|
services: ${answers.generateServices},
|
|
544
587
|
actions: false, // Actions require TypeScript
|
|
588
|
+
schemas: ${answers.generateSchemas},
|
|
589
|
+
upload: ${answers.generateUpload},
|
|
545
590
|
},
|
|
546
591
|
|
|
547
592
|
// Strapi version
|
|
@@ -555,7 +600,8 @@ const { defineConfig } = require("strapi2front");
|
|
|
555
600
|
module.exports = defineConfig({
|
|
556
601
|
// Strapi connection
|
|
557
602
|
url: process.env.STRAPI_URL || "${answers.strapiUrl}",
|
|
558
|
-
|
|
603
|
+
// Token for syncing schema (uses STRAPI_SYNC_TOKEN with fallback to STRAPI_TOKEN)
|
|
604
|
+
token: process.env.STRAPI_SYNC_TOKEN || process.env.STRAPI_TOKEN,
|
|
559
605
|
|
|
560
606
|
// API prefix (default: "/api")
|
|
561
607
|
apiPrefix: "${answers.apiPrefix}",
|
|
@@ -566,16 +612,15 @@ module.exports = defineConfig({
|
|
|
566
612
|
// Output configuration
|
|
567
613
|
output: {
|
|
568
614
|
path: "${answers.outputDir}",
|
|
569
|
-
types: "types",
|
|
570
|
-
services: "services",
|
|
571
|
-
structure: 'by-feature' // or 'by-layer'
|
|
572
615
|
},
|
|
573
616
|
|
|
574
617
|
// Features to generate
|
|
575
618
|
features: {
|
|
576
|
-
types:
|
|
619
|
+
types: ${answers.generateTypes},
|
|
577
620
|
services: ${answers.generateServices},
|
|
578
621
|
actions: false, // Actions require TypeScript
|
|
622
|
+
schemas: ${answers.generateSchemas},
|
|
623
|
+
upload: ${answers.generateUpload},
|
|
579
624
|
},
|
|
580
625
|
|
|
581
626
|
// Strapi version
|
|
@@ -583,7 +628,7 @@ module.exports = defineConfig({
|
|
|
583
628
|
});
|
|
584
629
|
`;
|
|
585
630
|
}
|
|
586
|
-
async function appendToEnvFile(envPath, variables) {
|
|
631
|
+
async function appendToEnvFile(envPath, variables, includeUploadComment = false) {
|
|
587
632
|
let content = "";
|
|
588
633
|
try {
|
|
589
634
|
content = await fs5.readFile(envPath, "utf-8");
|
|
@@ -596,6 +641,20 @@ async function appendToEnvFile(envPath, variables) {
|
|
|
596
641
|
const newLines = [];
|
|
597
642
|
for (const [key, value] of Object.entries(variables)) {
|
|
598
643
|
if (!existingKeys.has(key)) {
|
|
644
|
+
if (key === "STRAPI_SYNC_TOKEN") {
|
|
645
|
+
newLines.push("");
|
|
646
|
+
newLines.push("# Sync token: Used by strapi2front to sync schema (development only)");
|
|
647
|
+
newLines.push("# Permissions: content-type-builder (getContentTypes, getComponents), i18n (listLocales)");
|
|
648
|
+
newLines.push("# IMPORTANT: Do NOT deploy this token to production");
|
|
649
|
+
} else if (key === "STRAPI_TOKEN") {
|
|
650
|
+
newLines.push("");
|
|
651
|
+
newLines.push("# Frontend token: Used by your app to fetch content (production)");
|
|
652
|
+
newLines.push("# Configure with only the permissions your app needs");
|
|
653
|
+
} else if (key === "PUBLIC_STRAPI_UPLOAD_TOKEN" && includeUploadComment) {
|
|
654
|
+
newLines.push("");
|
|
655
|
+
newLines.push("# Upload token: Create in Strapi Admin > Settings > API Tokens");
|
|
656
|
+
newLines.push("# Set permissions: Upload > upload (only, no delete/update)");
|
|
657
|
+
}
|
|
599
658
|
newLines.push(`${key}=${value}`);
|
|
600
659
|
}
|
|
601
660
|
}
|
|
@@ -605,6 +664,33 @@ async function appendToEnvFile(envPath, variables) {
|
|
|
605
664
|
await fs5.writeFile(envPath, newContent, "utf-8");
|
|
606
665
|
}
|
|
607
666
|
}
|
|
667
|
+
async function createEnvExampleFile(envExamplePath, includeUpload = false) {
|
|
668
|
+
const lines = [
|
|
669
|
+
"# Strapi URL",
|
|
670
|
+
"STRAPI_URL=http://localhost:1337",
|
|
671
|
+
"",
|
|
672
|
+
"# Sync token: Used by strapi2front to sync schema (development only)",
|
|
673
|
+
"# Permissions: content-type-builder (getContentTypes, getComponents), i18n (listLocales)",
|
|
674
|
+
"# IMPORTANT: Do NOT deploy this token to production",
|
|
675
|
+
"STRAPI_SYNC_TOKEN=",
|
|
676
|
+
"",
|
|
677
|
+
"# Frontend token: Used by your app to fetch content (production)",
|
|
678
|
+
"# Configure with only the permissions your app needs",
|
|
679
|
+
"STRAPI_TOKEN="
|
|
680
|
+
];
|
|
681
|
+
if (includeUpload) {
|
|
682
|
+
lines.push(
|
|
683
|
+
"",
|
|
684
|
+
"# Public URL for browser-side uploads",
|
|
685
|
+
"PUBLIC_STRAPI_URL=http://localhost:1337",
|
|
686
|
+
"",
|
|
687
|
+
"# Upload token: Create in Strapi Admin > Settings > API Tokens",
|
|
688
|
+
"# Set permissions: Upload > upload (only, no delete/update)",
|
|
689
|
+
"PUBLIC_STRAPI_UPLOAD_TOKEN="
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
await fs5.writeFile(envExamplePath, lines.join("\n") + "\n", "utf-8");
|
|
693
|
+
}
|
|
608
694
|
var BLOCKS_RENDERER_PACKAGE = "@strapi/blocks-react-renderer";
|
|
609
695
|
function schemaHasBlocks(schema) {
|
|
610
696
|
const fieldsFound = [];
|
|
@@ -656,49 +742,9 @@ function installPackage(packageName, cwd) {
|
|
|
656
742
|
};
|
|
657
743
|
execSync(commands2[pm], { cwd, stdio: "inherit" });
|
|
658
744
|
}
|
|
659
|
-
function getOrphanedFolders(outputPath, currentStructure) {
|
|
660
|
-
const orphanedFolders = [];
|
|
661
|
-
if (currentStructure === "by-feature") {
|
|
662
|
-
const byLayerFolders = ["types", "services", "actions"];
|
|
663
|
-
for (const folder of byLayerFolders) {
|
|
664
|
-
const folderPath = path6.join(outputPath, folder);
|
|
665
|
-
if (fs6.existsSync(folderPath)) {
|
|
666
|
-
orphanedFolders.push(folder);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
if (fs6.existsSync(path6.join(outputPath, "client.ts"))) {
|
|
670
|
-
orphanedFolders.push("client.ts");
|
|
671
|
-
}
|
|
672
|
-
if (fs6.existsSync(path6.join(outputPath, "locales.ts"))) {
|
|
673
|
-
orphanedFolders.push("locales.ts");
|
|
674
|
-
}
|
|
675
|
-
} else {
|
|
676
|
-
const byFeatureFolders = ["collections", "singles", "shared", "components"];
|
|
677
|
-
for (const folder of byFeatureFolders) {
|
|
678
|
-
const folderPath = path6.join(outputPath, folder);
|
|
679
|
-
if (fs6.existsSync(folderPath)) {
|
|
680
|
-
orphanedFolders.push(folder);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
return orphanedFolders;
|
|
685
|
-
}
|
|
686
|
-
function cleanOrphanedFiles(outputPath, orphanedItems) {
|
|
687
|
-
for (const item of orphanedItems) {
|
|
688
|
-
const itemPath = path6.join(outputPath, item);
|
|
689
|
-
if (fs6.existsSync(itemPath)) {
|
|
690
|
-
const stat = fs6.statSync(itemPath);
|
|
691
|
-
if (stat.isDirectory()) {
|
|
692
|
-
fs6.rmSync(itemPath, { recursive: true, force: true });
|
|
693
|
-
} else {
|
|
694
|
-
fs6.unlinkSync(itemPath);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
745
|
async function syncCommand(options) {
|
|
700
746
|
const cwd = process.cwd();
|
|
701
|
-
p.intro(
|
|
747
|
+
p.intro(pc3.cyan("strapi2front sync"));
|
|
702
748
|
const s = p.spinner();
|
|
703
749
|
try {
|
|
704
750
|
s.start("Loading configuration...");
|
|
@@ -712,20 +758,20 @@ async function syncCommand(options) {
|
|
|
712
758
|
if (versionResult.detected !== config.strapiVersion) {
|
|
713
759
|
if (versionResult.detected === "v5" && config.strapiVersion === "v4") {
|
|
714
760
|
p.log.warn(
|
|
715
|
-
|
|
761
|
+
pc3.yellow(`Detected Strapi ${pc3.bold("v5")} but config has ${pc3.bold("v4")}. Using v5.`)
|
|
716
762
|
);
|
|
717
763
|
effectiveVersion = "v5";
|
|
718
764
|
} else if (versionResult.detected === "v4" && config.strapiVersion === "v5") {
|
|
719
765
|
p.log.warn(
|
|
720
|
-
|
|
766
|
+
pc3.yellow(`Detected Strapi ${pc3.bold("v4")} but config has ${pc3.bold("v5")}. Using v4.`)
|
|
721
767
|
);
|
|
722
768
|
effectiveVersion = "v4";
|
|
723
769
|
}
|
|
724
770
|
} else {
|
|
725
|
-
p.log.info(`Strapi ${
|
|
771
|
+
p.log.info(`Strapi ${pc3.green(pc3.bold(config.strapiVersion))}`);
|
|
726
772
|
}
|
|
727
773
|
} else {
|
|
728
|
-
p.log.warn(
|
|
774
|
+
p.log.warn(pc3.yellow(`Could not detect Strapi version. Using ${pc3.bold(config.strapiVersion)}`));
|
|
729
775
|
}
|
|
730
776
|
config = { ...config, strapiVersion: effectiveVersion };
|
|
731
777
|
s.start("Fetching schema from Strapi...");
|
|
@@ -735,9 +781,9 @@ async function syncCommand(options) {
|
|
|
735
781
|
let blocksRendererInstalled = isPackageInstalled(BLOCKS_RENDERER_PACKAGE, cwd);
|
|
736
782
|
const { hasBlocks: hasBlocksFields, fieldsFound: blocksFieldsFound } = schemaHasBlocks(schema);
|
|
737
783
|
if (hasBlocksFields && !blocksRendererInstalled) {
|
|
738
|
-
p.log.info(`Blocks fields detected: ${
|
|
784
|
+
p.log.info(`Blocks fields detected: ${pc3.cyan(blocksFieldsFound.join(", "))}`);
|
|
739
785
|
const installBlocks = await p.confirm({
|
|
740
|
-
message: `Install ${
|
|
786
|
+
message: `Install ${pc3.cyan(BLOCKS_RENDERER_PACKAGE)} for proper type support and rendering?`,
|
|
741
787
|
initialValue: true
|
|
742
788
|
});
|
|
743
789
|
if (p.isCancel(installBlocks)) {
|
|
@@ -755,145 +801,62 @@ async function syncCommand(options) {
|
|
|
755
801
|
logger.warn("You can install it manually later and re-run sync");
|
|
756
802
|
}
|
|
757
803
|
} else {
|
|
758
|
-
p.log.info(
|
|
804
|
+
p.log.info(pc3.dim(`Skipping ${BLOCKS_RENDERER_PACKAGE}. BlocksContent will be typed as unknown[]`));
|
|
759
805
|
}
|
|
760
806
|
}
|
|
761
807
|
const outputPath = path6.join(cwd, config.output.path);
|
|
762
808
|
const generatedFiles = [];
|
|
763
|
-
const generateAll = !options.typesOnly && !options.servicesOnly && !options.actionsOnly;
|
|
764
|
-
const isByFeature = config.output.structure === "by-feature";
|
|
765
|
-
const currentStructure = isByFeature ? "by-feature" : "by-layer";
|
|
766
|
-
if (fs6.existsSync(outputPath)) {
|
|
767
|
-
const orphanedFolders = getOrphanedFolders(outputPath, currentStructure);
|
|
768
|
-
if (orphanedFolders.length > 0) {
|
|
769
|
-
const otherStructure = isByFeature ? "by-layer" : "by-feature";
|
|
770
|
-
p.log.warn(
|
|
771
|
-
pc4.yellow(`Found files from previous ${pc4.bold(otherStructure)} structure:`)
|
|
772
|
-
);
|
|
773
|
-
p.log.message(pc4.dim(` ${orphanedFolders.join(", ")}`));
|
|
774
|
-
let shouldClean = options.clean;
|
|
775
|
-
if (!shouldClean) {
|
|
776
|
-
const cleanResponse = await p.confirm({
|
|
777
|
-
message: `Remove orphaned ${otherStructure} files?`,
|
|
778
|
-
initialValue: true
|
|
779
|
-
});
|
|
780
|
-
if (p.isCancel(cleanResponse)) {
|
|
781
|
-
p.cancel("Sync cancelled");
|
|
782
|
-
process.exit(0);
|
|
783
|
-
}
|
|
784
|
-
shouldClean = cleanResponse;
|
|
785
|
-
}
|
|
786
|
-
if (shouldClean) {
|
|
787
|
-
s.start("Cleaning orphaned files...");
|
|
788
|
-
cleanOrphanedFiles(outputPath, orphanedFolders);
|
|
789
|
-
s.stop(`Removed: ${orphanedFolders.join(", ")}`);
|
|
790
|
-
} else {
|
|
791
|
-
p.log.info(pc4.dim("Keeping orphaned files. You can clean them manually or use --clean flag."));
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
809
|
+
const generateAll = !options.typesOnly && !options.servicesOnly && !options.actionsOnly && !options.schemasOnly && !options.uploadOnly;
|
|
795
810
|
const outputFormat = config.outputFormat || "typescript";
|
|
796
811
|
let moduleType = "commonjs";
|
|
797
812
|
if (outputFormat === "jsdoc") {
|
|
798
813
|
if (config.moduleType) {
|
|
799
814
|
moduleType = config.moduleType;
|
|
800
|
-
p.log.info(`Module type: ${
|
|
815
|
+
p.log.info(`Module type: ${pc3.cyan(moduleType)} (from config)`);
|
|
801
816
|
} else {
|
|
802
817
|
const detected = await detectModuleType(cwd);
|
|
803
818
|
moduleType = detected.type;
|
|
804
|
-
p.log.info(`Module type: ${
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
if (isByFeature) {
|
|
808
|
-
s.start(`Generating files (by-feature, ${outputFormat})...`);
|
|
809
|
-
const files = await generateByFeature(schema, rawSchema.locales, {
|
|
810
|
-
outputDir: outputPath,
|
|
811
|
-
features: {
|
|
812
|
-
types: config.features.types && (generateAll || Boolean(options.typesOnly)),
|
|
813
|
-
services: config.features.services && (generateAll || Boolean(options.servicesOnly)),
|
|
814
|
-
actions: config.features.actions && outputFormat === "typescript" && (generateAll || Boolean(options.actionsOnly))
|
|
815
|
-
},
|
|
816
|
-
blocksRendererInstalled,
|
|
817
|
-
strapiVersion: config.strapiVersion,
|
|
818
|
-
apiPrefix: config.apiPrefix,
|
|
819
|
-
outputFormat,
|
|
820
|
-
moduleType
|
|
821
|
-
});
|
|
822
|
-
generatedFiles.push(...files);
|
|
823
|
-
s.stop(`Generated ${files.length} files`);
|
|
824
|
-
} else {
|
|
825
|
-
if (generateAll || options.typesOnly) {
|
|
826
|
-
if (config.features.types) {
|
|
827
|
-
s.start(`Generating types (${outputFormat})...`);
|
|
828
|
-
const typesPath = path6.join(outputPath, config.output.types);
|
|
829
|
-
const files = await generateTypes(schema, {
|
|
830
|
-
outputDir: typesPath,
|
|
831
|
-
blocksRendererInstalled,
|
|
832
|
-
strapiVersion: config.strapiVersion,
|
|
833
|
-
outputFormat
|
|
834
|
-
});
|
|
835
|
-
generatedFiles.push(...files);
|
|
836
|
-
s.stop(`Generated ${files.length} type files`);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
if (generateAll || options.servicesOnly) {
|
|
840
|
-
if (config.features.services) {
|
|
841
|
-
s.start("Generating client...");
|
|
842
|
-
const clientFiles = await generateClient({ outputDir: outputPath, strapiVersion: config.strapiVersion, apiPrefix: config.apiPrefix });
|
|
843
|
-
generatedFiles.push(...clientFiles);
|
|
844
|
-
s.stop("Generated client");
|
|
845
|
-
s.start("Generating locales...");
|
|
846
|
-
const localesFiles = await generateLocales(rawSchema.locales, { outputDir: outputPath });
|
|
847
|
-
generatedFiles.push(...localesFiles);
|
|
848
|
-
if (rawSchema.locales.length > 0) {
|
|
849
|
-
s.stop(`Generated locales: ${rawSchema.locales.map((l) => l.code).join(", ")}`);
|
|
850
|
-
} else {
|
|
851
|
-
s.stop("Generated locales (i18n not enabled in Strapi)");
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
if (generateAll || options.servicesOnly) {
|
|
856
|
-
if (config.features.services) {
|
|
857
|
-
s.start(`Generating services (${outputFormat})...`);
|
|
858
|
-
const servicesPath = path6.join(outputPath, config.output.services);
|
|
859
|
-
const typesImportPath = path6.relative(servicesPath, path6.join(outputPath, config.output.types)).replace(/\\/g, "/") || ".";
|
|
860
|
-
const files = await generateServices(schema, {
|
|
861
|
-
outputDir: servicesPath,
|
|
862
|
-
typesImportPath: typesImportPath.startsWith(".") ? typesImportPath : "./" + typesImportPath,
|
|
863
|
-
strapiVersion: config.strapiVersion,
|
|
864
|
-
outputFormat
|
|
865
|
-
});
|
|
866
|
-
generatedFiles.push(...files);
|
|
867
|
-
s.stop(`Generated ${files.length} service files`);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
if ((generateAll || options.actionsOnly) && outputFormat === "typescript") {
|
|
871
|
-
if (config.features.actions) {
|
|
872
|
-
s.start("Generating Astro actions...");
|
|
873
|
-
const actionsPath = path6.join(outputPath, config.output.actions);
|
|
874
|
-
const servicesPath = path6.join(outputPath, config.output.services);
|
|
875
|
-
const servicesImportPath = path6.relative(actionsPath, servicesPath).replace(/\\/g, "/") || ".";
|
|
876
|
-
const files = await generateActions(schema, {
|
|
877
|
-
outputDir: actionsPath,
|
|
878
|
-
servicesImportPath: servicesImportPath.startsWith(".") ? servicesImportPath : "./" + servicesImportPath,
|
|
879
|
-
strapiVersion: config.strapiVersion
|
|
880
|
-
});
|
|
881
|
-
generatedFiles.push(...files);
|
|
882
|
-
s.stop(`Generated ${files.length} action files`);
|
|
883
|
-
}
|
|
819
|
+
p.log.info(`Module type: ${pc3.cyan(moduleType)} (${detected.reason})`);
|
|
884
820
|
}
|
|
885
821
|
}
|
|
822
|
+
s.start(`Generating files (${outputFormat})...`);
|
|
823
|
+
const files = await generateByFeature(schema, rawSchema.locales, {
|
|
824
|
+
outputDir: outputPath,
|
|
825
|
+
features: {
|
|
826
|
+
types: config.features.types && (generateAll || Boolean(options.typesOnly)),
|
|
827
|
+
services: config.features.services && (generateAll || Boolean(options.servicesOnly)),
|
|
828
|
+
actions: config.features.actions && outputFormat === "typescript" && (generateAll || Boolean(options.actionsOnly)),
|
|
829
|
+
schemas: config.features.schemas && (generateAll || Boolean(options.schemasOnly)),
|
|
830
|
+
upload: config.features.upload && (generateAll || Boolean(options.uploadOnly))
|
|
831
|
+
},
|
|
832
|
+
schemaOptions: config.schemaOptions,
|
|
833
|
+
blocksRendererInstalled,
|
|
834
|
+
strapiVersion: config.strapiVersion,
|
|
835
|
+
apiPrefix: config.apiPrefix,
|
|
836
|
+
outputFormat,
|
|
837
|
+
moduleType
|
|
838
|
+
});
|
|
839
|
+
generatedFiles.push(...files);
|
|
840
|
+
s.stop(`Generated ${files.length} files`);
|
|
886
841
|
p.note(
|
|
887
842
|
[
|
|
888
|
-
`Generated ${generatedFiles.length} files in ${
|
|
843
|
+
`Generated ${generatedFiles.length} files in ${pc3.cyan(config.output.path)}`,
|
|
889
844
|
"",
|
|
890
845
|
"Files generated:",
|
|
891
|
-
...generatedFiles.slice(0, 10).map((f) => ` ${
|
|
892
|
-
generatedFiles.length > 10 ? ` ${
|
|
846
|
+
...generatedFiles.slice(0, 10).map((f) => ` ${pc3.dim(path6.relative(cwd, f))}`),
|
|
847
|
+
generatedFiles.length > 10 ? ` ${pc3.dim(`... and ${generatedFiles.length - 10} more`)}` : "",
|
|
848
|
+
"",
|
|
849
|
+
`Docs: ${pc3.dim("https://strapi2front.dev/docs")}`
|
|
893
850
|
].filter(Boolean).join("\n"),
|
|
894
851
|
"Sync complete!"
|
|
895
852
|
);
|
|
896
|
-
|
|
853
|
+
const generatedFeatures = [];
|
|
854
|
+
if (config.features.types) generatedFeatures.push("Types");
|
|
855
|
+
if (config.features.services) generatedFeatures.push("Services");
|
|
856
|
+
if (config.features.schemas) generatedFeatures.push("Schemas");
|
|
857
|
+
if (config.features.actions) generatedFeatures.push("Actions");
|
|
858
|
+
if (config.features.upload) generatedFeatures.push("Upload");
|
|
859
|
+
p.outro(pc3.green(`${generatedFeatures.join(", ")} ready to use!`));
|
|
897
860
|
} catch (error) {
|
|
898
861
|
s.stop("Sync failed");
|
|
899
862
|
if (error instanceof Error) {
|
|
@@ -904,6 +867,7 @@ async function syncCommand(options) {
|
|
|
904
867
|
} else {
|
|
905
868
|
logger.error("An unknown error occurred");
|
|
906
869
|
}
|
|
870
|
+
logger.info(`Docs: ${pc3.dim("https://strapi2front.dev/docs")}`);
|
|
907
871
|
process.exit(1);
|
|
908
872
|
}
|
|
909
873
|
}
|
|
@@ -918,14 +882,14 @@ var logo = `
|
|
|
918
882
|
|___/\\__|_| \\__,_| .__/|_|(_) |_| |_| \\___/|_| |_|\\__|
|
|
919
883
|
|_|
|
|
920
884
|
`;
|
|
921
|
-
program.name("strapi2front").description("Generate TypeScript types, services, and framework actions from your Strapi schema").version("0.1.0").addHelpText("beforeAll",
|
|
922
|
-
program.command("init").description("Initialize strapi2front in your project").option("-y, --yes", "Skip prompts and use defaults").option("--url <url>", "Strapi URL").option("--token <token>", "Strapi
|
|
923
|
-
program.command("sync").description("Sync types, services, and
|
|
885
|
+
program.name("strapi2front").description("Generate TypeScript types, services, and framework actions from your Strapi schema").version("0.1.0").addHelpText("beforeAll", pc3.cyan(logo));
|
|
886
|
+
program.command("init").description("Initialize strapi2front in your project").option("-y, --yes", "Skip prompts and use defaults").option("--url <url>", "Strapi URL").option("--token <token>", "Strapi sync token (STRAPI_SYNC_TOKEN)").option("--framework <framework>", "Framework to use (astro)").action(initCommand);
|
|
887
|
+
program.command("sync").description("Sync types, schemas, services, actions, and upload helpers from Strapi schema").option("-f, --force", "Force regeneration of all files").option("--types-only", "Only generate types").option("--services-only", "Only generate services").option("--actions-only", "Only generate actions").option("--schemas-only", "Only generate Zod validation schemas").option("--upload-only", "Only generate upload helpers").action(syncCommand);
|
|
924
888
|
var args = process.argv.slice(2);
|
|
925
889
|
var commands = ["init", "sync", "help", "--help", "-h", "--version", "-V"];
|
|
926
890
|
var hasCommand = args.some((arg) => commands.includes(arg));
|
|
927
891
|
if (args.length === 0 || !hasCommand) {
|
|
928
|
-
console.log(
|
|
892
|
+
console.log(pc3.cyan(logo));
|
|
929
893
|
initCommand();
|
|
930
894
|
} else {
|
|
931
895
|
program.parse();
|