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.
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import pc4 from 'picocolors';
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, generateTypes, generateClient, generateLocales, generateServices, generateActions } from '@strapi2front/generators';
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(pc4.cyan("strapi2front setup"));
177
+ p.intro(pc3.cyan("strapi2front setup"));
178
178
  p.note(
179
179
  [
180
- `Framework: ${pc4.green(getFrameworkDisplayName(detection.framework.name))} ${detection.framework.version ? pc4.dim(`v${detection.framework.version}`) : ""}`,
181
- `TypeScript: ${detection.typescript.enabled ? pc4.green("enabled") : pc4.yellow("disabled")}`,
182
- `Package Manager: ${pc4.green(detection.packageManager.name)}`
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(pc4.yellow(`Astro v${majorVersion} detected. Upgrade to v4+ to enable Actions.`));
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(pc4.dim("TypeScript not detected. Files will be generated as JavaScript with JSDoc annotations."));
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
- pc4.dim(
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 API token?",
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(pc4.dim("Token skipped. Remember to add STRAPI_TOKEN to your .env file later."));
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(pc4.dim(`Using Strapi ${strapiVersion}. This can be changed later in strapi.config.ts`));
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(pc4.dim(`Using custom API prefix: ${apiPrefix}`));
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 = canGenerateActions && isTypeScript ? ["types", "services", "actions"] : ["types", "services"];
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
- generateActions: canGenerateActions && isTypeScript && features.includes("actions"),
326
- generateServices: features.includes("services")
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(pc4.blue("i"), message);
361
+ console.log(pc3.blue("i"), message);
332
362
  },
333
363
  success: (message) => {
334
- console.log(pc4.green("v"), message);
364
+ console.log(pc3.green("v"), message);
335
365
  },
336
366
  warn: (message) => {
337
- console.log(pc4.yellow("!"), message);
367
+ console.log(pc3.yellow("!"), message);
338
368
  },
339
369
  error: (message) => {
340
- console.log(pc4.red("x"), message);
370
+ console.log(pc3.red("x"), message);
341
371
  },
342
372
  step: (message) => {
343
- console.log(pc4.cyan(">"), message);
373
+ console.log(pc3.cyan(">"), message);
344
374
  },
345
375
  dim: (message) => {
346
- console.log(pc4.dim(message));
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
- generateActions: answers.generateActions,
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
- await appendToEnvFile(envPath, {
446
+ const envVars = {
414
447
  STRAPI_URL: answers.strapiUrl,
415
- STRAPI_TOKEN: answers.strapiToken
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-sdk-js)?",
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... (${pc4.dim(installStrapi2frontCmd)})`);
473
+ s.start(`Installing strapi2front... (${pc3.dim(installStrapi2frontCmd)})`);
431
474
  try {
432
475
  await execAsync(installStrapi2frontCmd, cwd);
433
- s.stop(`${pc4.green("\u2713")} strapi2front installed`);
476
+ s.stop(`${pc3.green("\u2713")} strapi2front installed`);
434
477
  } catch {
435
- s.stop(`${pc4.red("\u2717")} Failed to install strapi2front`);
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-sdk-js");
439
- s.start(`Installing strapi-sdk-js... (${pc4.dim(installSdkCmd)})`);
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(`${pc4.green("\u2713")} strapi-sdk-js installed`);
485
+ s.stop(`${pc3.green("\u2713")} @strapi/client installed`);
443
486
  } catch {
444
- s.stop(`${pc4.red("\u2717")} Failed to install strapi-sdk-js`);
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(pc4.dim("Remember to install dependencies manually:"));
449
- p.log.info(pc4.dim(` ${getInstallDevCommand(packageManager.name, "strapi2front")}`));
450
- p.log.info(pc4.dim(` ${getInstallCommand(packageManager.name, "strapi-sdk-js")}`));
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
- `${pc4.green("\u2713")} Created ${pc4.cyan(configFileName)}`,
457
- `${pc4.green("\u2713")} Updated ${pc4.cyan(".env")} with Strapi credentials`,
458
- `${pc4.green("\u2713")} Created output directory ${pc4.cyan(answers.outputDir)}`,
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: ${pc4.cyan(answers.outputFormat === "jsdoc" ? "JavaScript (JSDoc)" : "TypeScript")}`,
504
+ `Output format: ${pc3.cyan(answers.outputFormat === "jsdoc" ? "JavaScript (JSDoc)" : "TypeScript")}`,
461
505
  "",
462
506
  `Next steps:`,
463
- ` 1. Run ${pc4.cyan("npx strapi2front sync")} to generate files`,
464
- ` 2. Import from ${pc4.cyan(answers.outputDir + "/collections/*" + fileExt)}`,
465
- ` 3. Import services from ${pc4.cyan(answers.outputDir + "/collections/*.service" + fileExt)}`
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(pc4.green("Happy coding!"));
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
- token: process.env.STRAPI_TOKEN,
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: true,
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
- token: process.env.STRAPI_TOKEN,
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: true,
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
- token: process.env.STRAPI_TOKEN,
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: true,
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(pc4.cyan("strapi2front sync"));
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
- pc4.yellow(`Detected Strapi ${pc4.bold("v5")} but config has ${pc4.bold("v4")}. Using v5.`)
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
- pc4.yellow(`Detected Strapi ${pc4.bold("v4")} but config has ${pc4.bold("v5")}. Using v4.`)
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 ${pc4.green(pc4.bold(config.strapiVersion))}`);
771
+ p.log.info(`Strapi ${pc3.green(pc3.bold(config.strapiVersion))}`);
726
772
  }
727
773
  } else {
728
- p.log.warn(pc4.yellow(`Could not detect Strapi version. Using ${pc4.bold(config.strapiVersion)}`));
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: ${pc4.cyan(blocksFieldsFound.join(", "))}`);
784
+ p.log.info(`Blocks fields detected: ${pc3.cyan(blocksFieldsFound.join(", "))}`);
739
785
  const installBlocks = await p.confirm({
740
- message: `Install ${pc4.cyan(BLOCKS_RENDERER_PACKAGE)} for proper type support and rendering?`,
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(pc4.dim(`Skipping ${BLOCKS_RENDERER_PACKAGE}. BlocksContent will be typed as unknown[]`));
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: ${pc4.cyan(moduleType)} (from config)`);
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: ${pc4.cyan(moduleType)} (${detected.reason})`);
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 ${pc4.cyan(config.output.path)}`,
843
+ `Generated ${generatedFiles.length} files in ${pc3.cyan(config.output.path)}`,
889
844
  "",
890
845
  "Files generated:",
891
- ...generatedFiles.slice(0, 10).map((f) => ` ${pc4.dim(path6.relative(cwd, f))}`),
892
- generatedFiles.length > 10 ? ` ${pc4.dim(`... and ${generatedFiles.length - 10} more`)}` : ""
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
- p.outro(pc4.green("Types and services are ready to use!"));
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", pc4.cyan(logo));
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 API token").option("--framework <framework>", "Framework to use (astro)").action(initCommand);
923
- program.command("sync").description("Sync types, services, and actions 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("--clean", "Automatically remove orphaned files from previous structure").action(syncCommand);
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(pc4.cyan(logo));
892
+ console.log(pc3.cyan(logo));
929
893
  initCommand();
930
894
  } else {
931
895
  program.parse();