strapi2front 0.4.1 → 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";
@@ -220,9 +220,22 @@ async function runInitPrompts(detection) {
220
220
  return null;
221
221
  }
222
222
  const strapiUrl = (strapiUrlInput || "").trim() || defaultUrl;
223
+ p.log.message(
224
+ pc3.dim(
225
+ `
226
+ To generate a token: Strapi Admin > Settings > API Tokens > Create new API Token
227
+ Required permissions:
228
+ Content-type-builder:
229
+ - Components: getComponents, getComponent
230
+ - Content-types: getContentTypes, getContentType
231
+ I18n (if using localization):
232
+ - Locales: listLocales
233
+ `
234
+ )
235
+ );
223
236
  const strapiToken = await p.text({
224
- message: "What is your Strapi API token?",
225
- 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)"
226
239
  });
227
240
  if (p.isCancel(strapiToken)) {
228
241
  p.cancel("Setup cancelled");
@@ -230,7 +243,7 @@ async function runInitPrompts(detection) {
230
243
  }
231
244
  const trimmedToken = (strapiToken || "").trim();
232
245
  if (trimmedToken === "") {
233
- 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."));
234
247
  }
235
248
  const strapiVersion = await p.select({
236
249
  message: "What version of Strapi are you using?",
@@ -244,7 +257,7 @@ async function runInitPrompts(detection) {
244
257
  p.cancel("Setup cancelled");
245
258
  return null;
246
259
  }
247
- 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`));
248
261
  const defaultPrefix = "/api";
249
262
  const apiPrefixInput = await p.text({
250
263
  message: "What is your Strapi API prefix?",
@@ -264,7 +277,7 @@ async function runInitPrompts(detection) {
264
277
  }
265
278
  const apiPrefix = (apiPrefixInput || "").trim() || defaultPrefix;
266
279
  if (apiPrefix !== defaultPrefix) {
267
- p.log.info(pc4.dim(`Using custom API prefix: ${apiPrefix}`));
280
+ p.log.info(pc3.dim(`Using custom API prefix: ${apiPrefix}`));
268
281
  }
269
282
  const outputDir = await p.text({
270
283
  message: "Where should we generate the Strapi files?",
@@ -288,10 +301,24 @@ async function runInitPrompts(detection) {
288
301
  hint: isTypeScript ? "Typed service functions for data fetching" : "Service functions with JSDoc annotations"
289
302
  }
290
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
+ });
291
316
  if (canGenerateActions && isTypeScript) {
292
317
  featureOptions.push({ value: "actions", label: "Astro Actions", hint: "Type-safe actions for client/server" });
293
318
  }
294
- 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");
295
322
  const features = await p.multiselect({
296
323
  message: "What would you like to generate?",
297
324
  options: featureOptions,
@@ -302,6 +329,19 @@ async function runInitPrompts(detection) {
302
329
  p.cancel("Setup cancelled");
303
330
  return null;
304
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
+ }
305
345
  return {
306
346
  strapiUrl,
307
347
  strapiToken: trimmedToken,
@@ -309,28 +349,31 @@ async function runInitPrompts(detection) {
309
349
  apiPrefix,
310
350
  outputFormat,
311
351
  outputDir: (outputDir || "").trim() || "src/strapi",
312
- generateActions: canGenerateActions && isTypeScript && features.includes("actions"),
313
- 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")
314
357
  };
315
358
  }
316
359
  var logger = {
317
360
  info: (message) => {
318
- console.log(pc4.blue("i"), message);
361
+ console.log(pc3.blue("i"), message);
319
362
  },
320
363
  success: (message) => {
321
- console.log(pc4.green("v"), message);
364
+ console.log(pc3.green("v"), message);
322
365
  },
323
366
  warn: (message) => {
324
- console.log(pc4.yellow("!"), message);
367
+ console.log(pc3.yellow("!"), message);
325
368
  },
326
369
  error: (message) => {
327
- console.log(pc4.red("x"), message);
370
+ console.log(pc3.red("x"), message);
328
371
  },
329
372
  step: (message) => {
330
- console.log(pc4.cyan(">"), message);
373
+ console.log(pc3.cyan(">"), message);
331
374
  },
332
375
  dim: (message) => {
333
- console.log(pc4.dim(message));
376
+ console.log(pc3.dim(message));
334
377
  },
335
378
  newLine: () => {
336
379
  console.log("");
@@ -390,22 +433,35 @@ async function initCommand(_options) {
390
433
  apiPrefix: answers.apiPrefix,
391
434
  outputFormat: answers.outputFormat,
392
435
  outputDir: answers.outputDir,
393
- generateActions: answers.generateActions,
436
+ generateTypes: answers.generateTypes,
394
437
  generateServices: answers.generateServices,
438
+ generateActions: answers.generateActions,
439
+ generateSchemas: answers.generateSchemas,
440
+ generateUpload: answers.generateUpload,
395
441
  moduleType
396
442
  });
397
443
  const configPath = path6.join(cwd, `strapi.config.${configExtension}`);
398
444
  await fs5.writeFile(configPath, configContent, "utf-8");
399
445
  const envPath = path6.join(cwd, ".env");
400
- await appendToEnvFile(envPath, {
446
+ const envVars = {
401
447
  STRAPI_URL: answers.strapiUrl,
402
- STRAPI_TOKEN: answers.strapiToken
403
- });
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);
404
460
  const outputPath = path6.join(cwd, answers.outputDir);
405
461
  await fs5.mkdir(outputPath, { recursive: true });
406
462
  s.stop("Configuration files created");
407
463
  const installDeps = await p.confirm({
408
- message: "Install required dependencies (strapi2front, strapi-sdk-js)?",
464
+ message: "Install required dependencies (strapi2front, @strapi/client)?",
409
465
  initialValue: true
410
466
  });
411
467
  if (p.isCancel(installDeps)) {
@@ -414,49 +470,52 @@ async function initCommand(_options) {
414
470
  }
415
471
  if (installDeps) {
416
472
  const installStrapi2frontCmd = getInstallDevCommand(packageManager.name, "strapi2front");
417
- s.start(`Installing strapi2front... (${pc4.dim(installStrapi2frontCmd)})`);
473
+ s.start(`Installing strapi2front... (${pc3.dim(installStrapi2frontCmd)})`);
418
474
  try {
419
475
  await execAsync(installStrapi2frontCmd, cwd);
420
- s.stop(`${pc4.green("\u2713")} strapi2front installed`);
476
+ s.stop(`${pc3.green("\u2713")} strapi2front installed`);
421
477
  } catch {
422
- s.stop(`${pc4.red("\u2717")} Failed to install strapi2front`);
478
+ s.stop(`${pc3.red("\u2717")} Failed to install strapi2front`);
423
479
  logger.warn(`Please install manually: ${installStrapi2frontCmd}`);
424
480
  }
425
- const installSdkCmd = getInstallCommand(packageManager.name, "strapi-sdk-js");
426
- 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)})`);
427
483
  try {
428
484
  await execAsync(installSdkCmd, cwd);
429
- s.stop(`${pc4.green("\u2713")} strapi-sdk-js installed`);
485
+ s.stop(`${pc3.green("\u2713")} @strapi/client installed`);
430
486
  } catch {
431
- s.stop(`${pc4.red("\u2717")} Failed to install strapi-sdk-js`);
487
+ s.stop(`${pc3.red("\u2717")} Failed to install @strapi/client`);
432
488
  logger.warn(`Please install manually: ${installSdkCmd}`);
433
489
  }
434
490
  } else {
435
- p.log.info(pc4.dim("Remember to install dependencies manually:"));
436
- p.log.info(pc4.dim(` ${getInstallDevCommand(packageManager.name, "strapi2front")}`));
437
- 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")}`));
438
494
  }
439
495
  const configFileName = `strapi.config.${configExtension}`;
440
496
  const fileExt = answers.outputFormat === "jsdoc" ? ".js" : ".ts";
441
497
  p.note(
442
498
  [
443
- `${pc4.green("\u2713")} Created ${pc4.cyan(configFileName)}`,
444
- `${pc4.green("\u2713")} Updated ${pc4.cyan(".env")} with Strapi credentials`,
445
- `${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)}`,
446
503
  "",
447
- `Output format: ${pc4.cyan(answers.outputFormat === "jsdoc" ? "JavaScript (JSDoc)" : "TypeScript")}`,
504
+ `Output format: ${pc3.cyan(answers.outputFormat === "jsdoc" ? "JavaScript (JSDoc)" : "TypeScript")}`,
448
505
  "",
449
506
  `Next steps:`,
450
- ` 1. Run ${pc4.cyan("npx strapi2front sync")} to generate files`,
451
- ` 2. Import from ${pc4.cyan(answers.outputDir + "/collections/*" + fileExt)}`,
452
- ` 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")}`
453
511
  ].join("\n"),
454
512
  "Setup complete!"
455
513
  );
456
- p.outro(pc4.green("Happy coding!"));
514
+ p.outro(pc3.green("Happy coding!"));
457
515
  } catch (error) {
458
516
  s.stop("Failed to create configuration files");
459
517
  logger.error(error instanceof Error ? error.message : "Unknown error");
518
+ logger.info(`Docs: ${pc3.dim("https://strapi2front.dev/docs/installation")}`);
460
519
  process.exit(1);
461
520
  }
462
521
  }
@@ -469,7 +528,8 @@ function generateConfigFile(answers) {
469
528
  export default defineConfig({
470
529
  // Strapi connection
471
530
  url: process.env.STRAPI_URL || "${answers.strapiUrl}",
472
- 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,
473
533
 
474
534
  // API prefix (default: "/api")
475
535
  apiPrefix: "${answers.apiPrefix}",
@@ -480,17 +540,15 @@ export default defineConfig({
480
540
  // Output configuration
481
541
  output: {
482
542
  path: "${answers.outputDir}",
483
- types: "types",
484
- services: "services",
485
- actions: "actions/strapi",
486
- structure: 'by-feature' // or 'by-layer'
487
543
  },
488
544
 
489
545
  // Features to generate
490
546
  features: {
491
- types: true,
547
+ types: ${answers.generateTypes},
492
548
  services: ${answers.generateServices},
493
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)
494
552
  },
495
553
 
496
554
  // Strapi version
@@ -505,7 +563,8 @@ import { defineConfig } from "strapi2front";
505
563
  export default defineConfig({
506
564
  // Strapi connection
507
565
  url: process.env.STRAPI_URL || "${answers.strapiUrl}",
508
- 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,
509
568
 
510
569
  // API prefix (default: "/api")
511
570
  apiPrefix: "${answers.apiPrefix}",
@@ -519,16 +578,15 @@ export default defineConfig({
519
578
  // Output configuration
520
579
  output: {
521
580
  path: "${answers.outputDir}",
522
- types: "types",
523
- services: "services",
524
- structure: 'by-feature' // or 'by-layer'
525
581
  },
526
582
 
527
583
  // Features to generate
528
584
  features: {
529
- types: true,
585
+ types: ${answers.generateTypes},
530
586
  services: ${answers.generateServices},
531
587
  actions: false, // Actions require TypeScript
588
+ schemas: ${answers.generateSchemas},
589
+ upload: ${answers.generateUpload},
532
590
  },
533
591
 
534
592
  // Strapi version
@@ -542,7 +600,8 @@ const { defineConfig } = require("strapi2front");
542
600
  module.exports = defineConfig({
543
601
  // Strapi connection
544
602
  url: process.env.STRAPI_URL || "${answers.strapiUrl}",
545
- 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,
546
605
 
547
606
  // API prefix (default: "/api")
548
607
  apiPrefix: "${answers.apiPrefix}",
@@ -553,16 +612,15 @@ module.exports = defineConfig({
553
612
  // Output configuration
554
613
  output: {
555
614
  path: "${answers.outputDir}",
556
- types: "types",
557
- services: "services",
558
- structure: 'by-feature' // or 'by-layer'
559
615
  },
560
616
 
561
617
  // Features to generate
562
618
  features: {
563
- types: true,
619
+ types: ${answers.generateTypes},
564
620
  services: ${answers.generateServices},
565
621
  actions: false, // Actions require TypeScript
622
+ schemas: ${answers.generateSchemas},
623
+ upload: ${answers.generateUpload},
566
624
  },
567
625
 
568
626
  // Strapi version
@@ -570,7 +628,7 @@ module.exports = defineConfig({
570
628
  });
571
629
  `;
572
630
  }
573
- async function appendToEnvFile(envPath, variables) {
631
+ async function appendToEnvFile(envPath, variables, includeUploadComment = false) {
574
632
  let content = "";
575
633
  try {
576
634
  content = await fs5.readFile(envPath, "utf-8");
@@ -583,6 +641,20 @@ async function appendToEnvFile(envPath, variables) {
583
641
  const newLines = [];
584
642
  for (const [key, value] of Object.entries(variables)) {
585
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
+ }
586
658
  newLines.push(`${key}=${value}`);
587
659
  }
588
660
  }
@@ -592,6 +664,33 @@ async function appendToEnvFile(envPath, variables) {
592
664
  await fs5.writeFile(envPath, newContent, "utf-8");
593
665
  }
594
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
+ }
595
694
  var BLOCKS_RENDERER_PACKAGE = "@strapi/blocks-react-renderer";
596
695
  function schemaHasBlocks(schema) {
597
696
  const fieldsFound = [];
@@ -643,49 +742,9 @@ function installPackage(packageName, cwd) {
643
742
  };
644
743
  execSync(commands2[pm], { cwd, stdio: "inherit" });
645
744
  }
646
- function getOrphanedFolders(outputPath, currentStructure) {
647
- const orphanedFolders = [];
648
- if (currentStructure === "by-feature") {
649
- const byLayerFolders = ["types", "services", "actions"];
650
- for (const folder of byLayerFolders) {
651
- const folderPath = path6.join(outputPath, folder);
652
- if (fs6.existsSync(folderPath)) {
653
- orphanedFolders.push(folder);
654
- }
655
- }
656
- if (fs6.existsSync(path6.join(outputPath, "client.ts"))) {
657
- orphanedFolders.push("client.ts");
658
- }
659
- if (fs6.existsSync(path6.join(outputPath, "locales.ts"))) {
660
- orphanedFolders.push("locales.ts");
661
- }
662
- } else {
663
- const byFeatureFolders = ["collections", "singles", "shared", "components"];
664
- for (const folder of byFeatureFolders) {
665
- const folderPath = path6.join(outputPath, folder);
666
- if (fs6.existsSync(folderPath)) {
667
- orphanedFolders.push(folder);
668
- }
669
- }
670
- }
671
- return orphanedFolders;
672
- }
673
- function cleanOrphanedFiles(outputPath, orphanedItems) {
674
- for (const item of orphanedItems) {
675
- const itemPath = path6.join(outputPath, item);
676
- if (fs6.existsSync(itemPath)) {
677
- const stat = fs6.statSync(itemPath);
678
- if (stat.isDirectory()) {
679
- fs6.rmSync(itemPath, { recursive: true, force: true });
680
- } else {
681
- fs6.unlinkSync(itemPath);
682
- }
683
- }
684
- }
685
- }
686
745
  async function syncCommand(options) {
687
746
  const cwd = process.cwd();
688
- p.intro(pc4.cyan("strapi2front sync"));
747
+ p.intro(pc3.cyan("strapi2front sync"));
689
748
  const s = p.spinner();
690
749
  try {
691
750
  s.start("Loading configuration...");
@@ -699,20 +758,20 @@ async function syncCommand(options) {
699
758
  if (versionResult.detected !== config.strapiVersion) {
700
759
  if (versionResult.detected === "v5" && config.strapiVersion === "v4") {
701
760
  p.log.warn(
702
- 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.`)
703
762
  );
704
763
  effectiveVersion = "v5";
705
764
  } else if (versionResult.detected === "v4" && config.strapiVersion === "v5") {
706
765
  p.log.warn(
707
- 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.`)
708
767
  );
709
768
  effectiveVersion = "v4";
710
769
  }
711
770
  } else {
712
- p.log.info(`Strapi ${pc4.green(pc4.bold(config.strapiVersion))}`);
771
+ p.log.info(`Strapi ${pc3.green(pc3.bold(config.strapiVersion))}`);
713
772
  }
714
773
  } else {
715
- 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)}`));
716
775
  }
717
776
  config = { ...config, strapiVersion: effectiveVersion };
718
777
  s.start("Fetching schema from Strapi...");
@@ -722,9 +781,9 @@ async function syncCommand(options) {
722
781
  let blocksRendererInstalled = isPackageInstalled(BLOCKS_RENDERER_PACKAGE, cwd);
723
782
  const { hasBlocks: hasBlocksFields, fieldsFound: blocksFieldsFound } = schemaHasBlocks(schema);
724
783
  if (hasBlocksFields && !blocksRendererInstalled) {
725
- p.log.info(`Blocks fields detected: ${pc4.cyan(blocksFieldsFound.join(", "))}`);
784
+ p.log.info(`Blocks fields detected: ${pc3.cyan(blocksFieldsFound.join(", "))}`);
726
785
  const installBlocks = await p.confirm({
727
- 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?`,
728
787
  initialValue: true
729
788
  });
730
789
  if (p.isCancel(installBlocks)) {
@@ -742,145 +801,62 @@ async function syncCommand(options) {
742
801
  logger.warn("You can install it manually later and re-run sync");
743
802
  }
744
803
  } else {
745
- 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[]`));
746
805
  }
747
806
  }
748
807
  const outputPath = path6.join(cwd, config.output.path);
749
808
  const generatedFiles = [];
750
- const generateAll = !options.typesOnly && !options.servicesOnly && !options.actionsOnly;
751
- const isByFeature = config.output.structure === "by-feature";
752
- const currentStructure = isByFeature ? "by-feature" : "by-layer";
753
- if (fs6.existsSync(outputPath)) {
754
- const orphanedFolders = getOrphanedFolders(outputPath, currentStructure);
755
- if (orphanedFolders.length > 0) {
756
- const otherStructure = isByFeature ? "by-layer" : "by-feature";
757
- p.log.warn(
758
- pc4.yellow(`Found files from previous ${pc4.bold(otherStructure)} structure:`)
759
- );
760
- p.log.message(pc4.dim(` ${orphanedFolders.join(", ")}`));
761
- let shouldClean = options.clean;
762
- if (!shouldClean) {
763
- const cleanResponse = await p.confirm({
764
- message: `Remove orphaned ${otherStructure} files?`,
765
- initialValue: true
766
- });
767
- if (p.isCancel(cleanResponse)) {
768
- p.cancel("Sync cancelled");
769
- process.exit(0);
770
- }
771
- shouldClean = cleanResponse;
772
- }
773
- if (shouldClean) {
774
- s.start("Cleaning orphaned files...");
775
- cleanOrphanedFiles(outputPath, orphanedFolders);
776
- s.stop(`Removed: ${orphanedFolders.join(", ")}`);
777
- } else {
778
- p.log.info(pc4.dim("Keeping orphaned files. You can clean them manually or use --clean flag."));
779
- }
780
- }
781
- }
809
+ const generateAll = !options.typesOnly && !options.servicesOnly && !options.actionsOnly && !options.schemasOnly && !options.uploadOnly;
782
810
  const outputFormat = config.outputFormat || "typescript";
783
811
  let moduleType = "commonjs";
784
812
  if (outputFormat === "jsdoc") {
785
813
  if (config.moduleType) {
786
814
  moduleType = config.moduleType;
787
- p.log.info(`Module type: ${pc4.cyan(moduleType)} (from config)`);
815
+ p.log.info(`Module type: ${pc3.cyan(moduleType)} (from config)`);
788
816
  } else {
789
817
  const detected = await detectModuleType(cwd);
790
818
  moduleType = detected.type;
791
- p.log.info(`Module type: ${pc4.cyan(moduleType)} (${detected.reason})`);
792
- }
793
- }
794
- if (isByFeature) {
795
- s.start(`Generating files (by-feature, ${outputFormat})...`);
796
- const files = await generateByFeature(schema, rawSchema.locales, {
797
- outputDir: outputPath,
798
- features: {
799
- types: config.features.types && (generateAll || Boolean(options.typesOnly)),
800
- services: config.features.services && (generateAll || Boolean(options.servicesOnly)),
801
- actions: config.features.actions && outputFormat === "typescript" && (generateAll || Boolean(options.actionsOnly))
802
- },
803
- blocksRendererInstalled,
804
- strapiVersion: config.strapiVersion,
805
- apiPrefix: config.apiPrefix,
806
- outputFormat,
807
- moduleType
808
- });
809
- generatedFiles.push(...files);
810
- s.stop(`Generated ${files.length} files`);
811
- } else {
812
- if (generateAll || options.typesOnly) {
813
- if (config.features.types) {
814
- s.start(`Generating types (${outputFormat})...`);
815
- const typesPath = path6.join(outputPath, config.output.types);
816
- const files = await generateTypes(schema, {
817
- outputDir: typesPath,
818
- blocksRendererInstalled,
819
- strapiVersion: config.strapiVersion,
820
- outputFormat
821
- });
822
- generatedFiles.push(...files);
823
- s.stop(`Generated ${files.length} type files`);
824
- }
825
- }
826
- if (generateAll || options.servicesOnly) {
827
- if (config.features.services) {
828
- s.start("Generating client...");
829
- const clientFiles = await generateClient({ outputDir: outputPath, strapiVersion: config.strapiVersion, apiPrefix: config.apiPrefix });
830
- generatedFiles.push(...clientFiles);
831
- s.stop("Generated client");
832
- s.start("Generating locales...");
833
- const localesFiles = await generateLocales(rawSchema.locales, { outputDir: outputPath });
834
- generatedFiles.push(...localesFiles);
835
- if (rawSchema.locales.length > 0) {
836
- s.stop(`Generated locales: ${rawSchema.locales.map((l) => l.code).join(", ")}`);
837
- } else {
838
- s.stop("Generated locales (i18n not enabled in Strapi)");
839
- }
840
- }
841
- }
842
- if (generateAll || options.servicesOnly) {
843
- if (config.features.services) {
844
- s.start(`Generating services (${outputFormat})...`);
845
- const servicesPath = path6.join(outputPath, config.output.services);
846
- const typesImportPath = path6.relative(servicesPath, path6.join(outputPath, config.output.types)).replace(/\\/g, "/") || ".";
847
- const files = await generateServices(schema, {
848
- outputDir: servicesPath,
849
- typesImportPath: typesImportPath.startsWith(".") ? typesImportPath : "./" + typesImportPath,
850
- strapiVersion: config.strapiVersion,
851
- outputFormat
852
- });
853
- generatedFiles.push(...files);
854
- s.stop(`Generated ${files.length} service files`);
855
- }
856
- }
857
- if ((generateAll || options.actionsOnly) && outputFormat === "typescript") {
858
- if (config.features.actions) {
859
- s.start("Generating Astro actions...");
860
- const actionsPath = path6.join(outputPath, config.output.actions);
861
- const servicesPath = path6.join(outputPath, config.output.services);
862
- const servicesImportPath = path6.relative(actionsPath, servicesPath).replace(/\\/g, "/") || ".";
863
- const files = await generateActions(schema, {
864
- outputDir: actionsPath,
865
- servicesImportPath: servicesImportPath.startsWith(".") ? servicesImportPath : "./" + servicesImportPath,
866
- strapiVersion: config.strapiVersion
867
- });
868
- generatedFiles.push(...files);
869
- s.stop(`Generated ${files.length} action files`);
870
- }
819
+ p.log.info(`Module type: ${pc3.cyan(moduleType)} (${detected.reason})`);
871
820
  }
872
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`);
873
841
  p.note(
874
842
  [
875
- `Generated ${generatedFiles.length} files in ${pc4.cyan(config.output.path)}`,
843
+ `Generated ${generatedFiles.length} files in ${pc3.cyan(config.output.path)}`,
876
844
  "",
877
845
  "Files generated:",
878
- ...generatedFiles.slice(0, 10).map((f) => ` ${pc4.dim(path6.relative(cwd, f))}`),
879
- 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")}`
880
850
  ].filter(Boolean).join("\n"),
881
851
  "Sync complete!"
882
852
  );
883
- 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!`));
884
860
  } catch (error) {
885
861
  s.stop("Sync failed");
886
862
  if (error instanceof Error) {
@@ -891,6 +867,7 @@ async function syncCommand(options) {
891
867
  } else {
892
868
  logger.error("An unknown error occurred");
893
869
  }
870
+ logger.info(`Docs: ${pc3.dim("https://strapi2front.dev/docs")}`);
894
871
  process.exit(1);
895
872
  }
896
873
  }
@@ -905,14 +882,14 @@ var logo = `
905
882
  |___/\\__|_| \\__,_| .__/|_|(_) |_| |_| \\___/|_| |_|\\__|
906
883
  |_|
907
884
  `;
908
- program.name("strapi2front").description("Generate TypeScript types, services, and framework actions from your Strapi schema").version("0.1.0").addHelpText("beforeAll", pc4.cyan(logo));
909
- 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);
910
- 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);
911
888
  var args = process.argv.slice(2);
912
889
  var commands = ["init", "sync", "help", "--help", "-h", "--version", "-V"];
913
890
  var hasCommand = args.some((arg) => commands.includes(arg));
914
891
  if (args.length === 0 || !hasCommand) {
915
- console.log(pc4.cyan(logo));
892
+ console.log(pc3.cyan(logo));
916
893
  initCommand();
917
894
  } else {
918
895
  program.parse();