create-nextjs-cms 0.5.63 → 0.5.66

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.
Files changed (74) hide show
  1. package/dist/index.js +50 -198
  2. package/dist/lib/cms-setup.d.ts +6 -0
  3. package/dist/lib/cms-setup.d.ts.map +1 -0
  4. package/dist/lib/cms-setup.js +30 -0
  5. package/dist/lib/exec-utils.d.ts +28 -0
  6. package/dist/lib/exec-utils.d.ts.map +1 -0
  7. package/dist/lib/exec-utils.js +61 -0
  8. package/dist/lib/global-package.d.ts +10 -0
  9. package/dist/lib/global-package.d.ts.map +1 -0
  10. package/dist/lib/global-package.js +158 -0
  11. package/dist/lib/install-deps.d.ts +4 -0
  12. package/dist/lib/install-deps.d.ts.map +1 -0
  13. package/dist/lib/install-deps.js +114 -0
  14. package/dist/lib/logger.d.ts +8 -0
  15. package/dist/lib/logger.d.ts.map +1 -0
  16. package/dist/lib/logger.js +18 -0
  17. package/dist/lib/section-creators.d.ts +13 -0
  18. package/dist/lib/section-creators.d.ts.map +1 -0
  19. package/dist/lib/section-creators.js +201 -0
  20. package/dist/lib/utils.js +1 -1
  21. package/package.json +4 -2
  22. package/templates/default/lib/postinstall.js +1 -1
  23. package/templates/default/package.json +1 -1
  24. package/templates/default/.uploads/.photos/categories/000e56fada378de1a84a4 +0 -0
  25. package/templates/default/.uploads/.photos/categories/121e7d13ee3b5fa03795b +0 -0
  26. package/templates/default/.uploads/.photos/categories/17a55c5a53eb62293c69e +0 -0
  27. package/templates/default/.uploads/.photos/categories/4c86ad8c1f51f1e3a953b +0 -0
  28. package/templates/default/.uploads/.photos/categories/4d360813741a45744327c +0 -0
  29. package/templates/default/.uploads/.photos/categories/5238fefaa3cbebf388178 +0 -0
  30. package/templates/default/.uploads/.photos/categories/541d7cf8c69895bcb15cd +0 -0
  31. package/templates/default/.uploads/.photos/categories/57a2f689a910a378247ea +0 -0
  32. package/templates/default/.uploads/.photos/categories/6de4bdb341a3e1f70ddc3 +0 -0
  33. package/templates/default/.uploads/.photos/categories/6f52739eaa686441a28f4 +0 -0
  34. package/templates/default/.uploads/.photos/categories/8a21b9fa8ecd88f460a15 +0 -0
  35. package/templates/default/.uploads/.photos/categories/91948abcb073c9445fdec +0 -0
  36. package/templates/default/.uploads/.photos/categories/9ae700d1abfd6b85780e8 +0 -0
  37. package/templates/default/.uploads/.photos/categories/9edee86e932985fc589b5 +0 -0
  38. package/templates/default/.uploads/.photos/categories/a520e77b082f35b575dba +0 -0
  39. package/templates/default/.uploads/.photos/categories/b19c449029330f0a74b20 +0 -0
  40. package/templates/default/.uploads/.photos/categories/b9802010f68afd4edb0e8 +0 -0
  41. package/templates/default/.uploads/.photos/categories/c1d7c3b986739bf496730 +0 -0
  42. package/templates/default/.uploads/.photos/categories/c25dc38567384513ffe93 +0 -0
  43. package/templates/default/.uploads/.photos/categories/cbe3874a3d13afba388df +0 -0
  44. package/templates/default/.uploads/.photos/categories/d450fdeb04f0d070442d6 +0 -0
  45. package/templates/default/.uploads/.photos/categories/f80bf3a4515680ead5a5c +0 -0
  46. package/templates/default/.uploads/.photos/categories/f8639c2d5b0d24cb76fb1 +0 -0
  47. package/templates/default/.uploads/.photos/categories/f8d997149d10aab046e40 +0 -0
  48. package/templates/default/.uploads/.photos/categories/fa2c55690ff96e33a16fe +0 -0
  49. package/templates/default/.uploads/.photos/featured_slider/d00be4edb4c38ca34b5a5 +0 -0
  50. package/templates/default/.uploads/.thumbs/categories/000e56fada378de1a84a4 +0 -0
  51. package/templates/default/.uploads/.thumbs/categories/121e7d13ee3b5fa03795b +0 -0
  52. package/templates/default/.uploads/.thumbs/categories/17a55c5a53eb62293c69e +0 -0
  53. package/templates/default/.uploads/.thumbs/categories/4c86ad8c1f51f1e3a953b +0 -0
  54. package/templates/default/.uploads/.thumbs/categories/4d360813741a45744327c +0 -0
  55. package/templates/default/.uploads/.thumbs/categories/5238fefaa3cbebf388178 +0 -0
  56. package/templates/default/.uploads/.thumbs/categories/541d7cf8c69895bcb15cd +0 -0
  57. package/templates/default/.uploads/.thumbs/categories/57a2f689a910a378247ea +0 -0
  58. package/templates/default/.uploads/.thumbs/categories/6de4bdb341a3e1f70ddc3 +0 -0
  59. package/templates/default/.uploads/.thumbs/categories/6f52739eaa686441a28f4 +0 -0
  60. package/templates/default/.uploads/.thumbs/categories/8a21b9fa8ecd88f460a15 +0 -0
  61. package/templates/default/.uploads/.thumbs/categories/91948abcb073c9445fdec +0 -0
  62. package/templates/default/.uploads/.thumbs/categories/9ae700d1abfd6b85780e8 +0 -0
  63. package/templates/default/.uploads/.thumbs/categories/9edee86e932985fc589b5 +0 -0
  64. package/templates/default/.uploads/.thumbs/categories/a520e77b082f35b575dba +0 -0
  65. package/templates/default/.uploads/.thumbs/categories/b19c449029330f0a74b20 +0 -0
  66. package/templates/default/.uploads/.thumbs/categories/b9802010f68afd4edb0e8 +0 -0
  67. package/templates/default/.uploads/.thumbs/categories/c1d7c3b986739bf496730 +0 -0
  68. package/templates/default/.uploads/.thumbs/categories/c25dc38567384513ffe93 +0 -0
  69. package/templates/default/.uploads/.thumbs/categories/cbe3874a3d13afba388df +0 -0
  70. package/templates/default/.uploads/.thumbs/categories/d450fdeb04f0d070442d6 +0 -0
  71. package/templates/default/.uploads/.thumbs/categories/f80bf3a4515680ead5a5c +0 -0
  72. package/templates/default/.uploads/.thumbs/categories/f8639c2d5b0d24cb76fb1 +0 -0
  73. package/templates/default/.uploads/.thumbs/categories/f8d997149d10aab046e40 +0 -0
  74. package/templates/default/.uploads/.thumbs/categories/fa2c55690ff96e33a16fe +0 -0
package/dist/index.js CHANGED
@@ -1,15 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'fs-extra';
3
- import { execSync } from 'node:child_process';
4
3
  import { fileURLToPath } from 'node:url';
5
4
  import { randomBytes } from 'node:crypto';
6
5
  import path, { dirname, resolve, relative, basename } from 'node:path';
7
6
  import { Command } from 'commander';
8
- import { text, confirm, spinner, log } from '@clack/prompts';
7
+ import { text, log } from '@clack/prompts';
9
8
  import { expandHome, isValidPkgName, isEmptyDir, detectPackageManager, validateTemplate } from './lib/utils.js';
10
9
  import { readFileSync } from 'node:fs';
11
10
  import chalk from 'chalk';
12
11
  import { renderTitle } from './lib/render-title.js';
12
+ import { installDependencies } from './lib/install-deps.js';
13
+ import { runCmsSetup } from './lib/cms-setup.js';
14
+ import { removeGlobalPackage, installGlobalPackage } from './lib/global-package.js';
15
+ import { createBlogSection, createCategorySection, createSimpleSection } from './lib/section-creators.js';
13
16
  /** Resolve __dirname for ESM */
14
17
  const __filename = fileURLToPath(import.meta.url);
15
18
  const __dirname = dirname(__filename);
@@ -23,80 +26,6 @@ const templateDir = fileURLToPath(new URL('../templates/default/', import.meta.u
23
26
  const handleSigTerm = () => process.exit(0);
24
27
  process.on('SIGINT', handleSigTerm);
25
28
  process.on('SIGTERM', handleSigTerm);
26
- /**
27
- * Creates a blog section file in the sections folder
28
- */
29
- async function createBlogSection(targetDir) {
30
- const sectionsDir = path.join(targetDir, 'sections');
31
- const blogSectionPath = path.join(sectionsDir, 'blog.section.ts');
32
- // Ensure sections directory exists
33
- await fs.ensureDir(sectionsDir);
34
- const blogSectionContent = `import { photoField, richTextField, textField, tagsField } from 'nextjs-cms/core/fields'
35
- import { hasItemsSection } from 'nextjs-cms/core/sections'
36
-
37
- const title = textField({
38
- name: 'title',
39
- label: 'Title',
40
- required: true,
41
- order: 1,
42
- })
43
-
44
- const coverPhotoField = photoField({
45
- name: 'coverphoto',
46
- label: 'Cover Photo',
47
- required: true,
48
- order: 2,
49
- fileType: ['jpg', 'jpeg', 'png', 'webp'],
50
- })
51
-
52
- const content = richTextField({
53
- name: 'content',
54
- label: 'Content',
55
- required: true,
56
- order: 3,
57
- })
58
-
59
-
60
- const keywords = tagsField({
61
- name: 'keywords',
62
- label: 'Keywords',
63
- required: false,
64
- order: 4,
65
- })
66
-
67
- export default hasItemsSection({
68
- name: 'blog',
69
- order: 1,
70
- icon: 'icon',
71
- readonly: false,
72
- headingField: title,
73
- title: {
74
- section: 'Blog',
75
- singular: 'Post',
76
- plural: 'Posts',
77
- },
78
- gallery: {
79
- db: {
80
- tableName: 'blog_gallery',
81
- identifierField: 'reference_id',
82
- photoNameField: 'photo',
83
- metaField: 'meta',
84
- },
85
- },
86
- db: {
87
- table: 'blog',
88
- },
89
- search: {
90
- searchFields: [title],
91
- },
92
- coverPhotoField: coverPhotoField,
93
- generateQR: false,
94
- fields: [title, coverPhotoField, content, keywords],
95
- })
96
- `;
97
- await fs.writeFile(blogSectionPath, blogSectionContent, 'utf-8');
98
- log.success('Blog section created successfully!');
99
- }
100
29
  /**
101
30
  * Generates a random secret string using crypto.randomBytes
102
31
  */
@@ -121,7 +50,7 @@ async function updateEnvSecrets(targetDir) {
121
50
  envContent = envContent.replace(/ACCESS_TOKEN_EXPIRATION=.*/, 'ACCESS_TOKEN_EXPIRATION=2h');
122
51
  envContent = envContent.replace(/REFRESH_TOKEN_EXPIRATION=.*/, 'REFRESH_TOKEN_EXPIRATION=1y');
123
52
  await fs.writeFile(envPath, envContent, 'utf-8');
124
- log.step('Generated random secrets and set token expirations in .env file');
53
+ log.message('Generated random secrets in .env file');
125
54
  }
126
55
  catch (e) {
127
56
  log.warn('Could not update .env secrets automatically.');
@@ -130,8 +59,7 @@ async function updateEnvSecrets(targetDir) {
130
59
  }
131
60
  async function createNextjsCms() {
132
61
  renderTitle();
133
- log.info('🚀 Welcome to NextJS CMS!');
134
- log.message('Creating your new CMS project...\n');
62
+ log.info('Create your new NextJS CMS project');
135
63
  let projectPath = '';
136
64
  const program = new Command(packageJson.name)
137
65
  .version(packageJson.version, '-v, --version', 'Output the current version of create-nextjs-cms.')
@@ -152,14 +80,14 @@ async function createNextjsCms() {
152
80
  targetDir: '',
153
81
  projectName: '',
154
82
  templateDir: templateDir,
155
- addBlogSection: false,
83
+ sectionsToAdd: [],
156
84
  };
157
85
  let targetIsCwd = false;
158
86
  try {
159
87
  // Ensure template folder is present in the published package
160
88
  if (!(await fs.pathExists(templateDir))) {
161
89
  log.error('Template directory not found in the published package.');
162
- log.message(' Make sure "templates/" is included in package.json#files and actually published.');
90
+ log.message('Please open an issue on the create-nextjs-cms repository: https://github.com/next-cms/create-nextjs-cms/issues');
163
91
  process.exit(1);
164
92
  }
165
93
  // Validate template structure
@@ -168,6 +96,7 @@ async function createNextjsCms() {
168
96
  if (!projectPath) {
169
97
  const res = await text({
170
98
  message: 'What is your project named?',
99
+ placeholder: 'my-cms-app',
171
100
  defaultValue: 'my-cms-app',
172
101
  validate: (name) => {
173
102
  const validation = isValidPkgName(basename(resolve(name)));
@@ -227,17 +156,20 @@ async function createNextjsCms() {
227
156
  await fs.ensureDir(options.targetDir);
228
157
  }
229
158
  }
230
- // Ask if user wants to add blog section
231
- const blogRes = await confirm({
232
- message: 'Would you like to add a default blog section config?',
233
- initialValue: false,
159
+ // Ask which sections to add
160
+ const { multiselect } = await import('@clack/prompts');
161
+ const sectionsRes = await multiselect({
162
+ message: 'Would you like to add default sections?',
163
+ options: [
164
+ { value: 'blog', label: 'Blog' },
165
+ { value: 'category', label: 'Category' },
166
+ { value: 'simple', label: 'Settings' },
167
+ ],
168
+ required: false,
234
169
  });
235
- options.addBlogSection = Boolean(blogRes);
170
+ options.sectionsToAdd = sectionsRes;
236
171
  log.step(`Creating project in: ${options.targetDir}`);
237
172
  // Copy template → project dir with a filter to skip build/cache artifacts
238
- const copySpinner = spinner();
239
- log.step('🔄 Copying template files...');
240
- copySpinner.start();
241
173
  await fs.copy(options.templateDir, options.targetDir, {
242
174
  filter: (src) => {
243
175
  const rel = relative(options.templateDir, src);
@@ -253,7 +185,6 @@ async function createNextjsCms() {
253
185
  overwrite: true,
254
186
  errorOnExist: false,
255
187
  });
256
- copySpinner.stop('✅ Template copied successfully!');
257
188
  // Generate random secrets for .env file
258
189
  await updateEnvSecrets(options.targetDir);
259
190
  // npm excludes .gitignore from packages, so we rename it back from _gitignore
@@ -261,12 +192,23 @@ async function createNextjsCms() {
261
192
  const gitignorePath = path.join(options.targetDir, '.gitignore');
262
193
  if (await fs.pathExists(gitignoreTemplatePath)) {
263
194
  await fs.move(gitignoreTemplatePath, gitignorePath);
264
- log.step('Restored .gitignore file');
265
195
  }
266
- // Create blog section if requested
267
- if (options.addBlogSection) {
268
- log.info('Creating blog section...');
269
- await createBlogSection(options.targetDir);
196
+ // Create selected sections
197
+ if (options.sectionsToAdd.length > 0) {
198
+ for (const section of options.sectionsToAdd) {
199
+ switch (section) {
200
+ case 'blog':
201
+ await createBlogSection(options.targetDir);
202
+ break;
203
+ case 'category':
204
+ await createCategorySection(options.targetDir);
205
+ break;
206
+ case 'simple':
207
+ await createSimpleSection(options.targetDir);
208
+ break;
209
+ }
210
+ }
211
+ log.message(`${options.sectionsToAdd.join(', ')} section${options.sectionsToAdd.length > 1 ? 's' : ''} added successfully`);
270
212
  }
271
213
  // Update package.json name (if template contains package.json)
272
214
  const packageJsonPath = path.join(options.targetDir, 'package.json');
@@ -275,123 +217,33 @@ async function createNextjsCms() {
275
217
  const pkg = (await fs.readJson(packageJsonPath));
276
218
  pkg.name = options.projectName;
277
219
  await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
278
- log.step(`Updated package.json name to: ${options.projectName}`);
279
220
  }
280
- catch (e) {
221
+ catch {
281
222
  log.warn('Could not update package.json name automatically.');
282
- log.error(` Error: ${e instanceof Error ? e.message : 'Unknown error'}`);
283
223
  }
284
224
  }
285
225
  else {
286
226
  log.warn('No package.json found in template root; skipping name update.');
287
227
  }
288
- log.info('Installing dependencies...');
289
228
  process.chdir(options.targetDir);
290
- const installSpinner = spinner();
291
- installSpinner.start('Installing dependencies...');
292
229
  const preferredPM = detectPackageManager();
293
- log.step(`Using ${chalk.green(preferredPM)} as the package manager...`);
294
230
  let installed = false;
295
- // Try preferred package manager first
296
- try {
297
- if (preferredPM === 'pnpm') {
298
- execSync('pnpm install', { stdio: 'inherit' });
299
- installed = true;
300
- installSpinner.stop('✅ Dependencies installed with pnpm!');
301
- }
302
- else if (preferredPM === 'yarn') {
303
- execSync('yarn install', { stdio: 'inherit' });
304
- installed = true;
305
- installSpinner.stop('✅ Dependencies installed with yarn!');
306
- }
307
- else if (preferredPM === 'bun') {
308
- execSync('bun install', { stdio: 'inherit' });
309
- installed = true;
310
- installSpinner.stop('✅ Dependencies installed with bun!');
311
- }
312
- else {
313
- execSync('npm install', { stdio: 'inherit' });
314
- installed = true;
315
- installSpinner.stop('✅ Dependencies installed with npm!');
316
- }
317
- }
318
- catch {
319
- installSpinner.message(`❌ ${preferredPM} install failed. Trying alternatives...`);
320
- // Fallback to other package managers
321
- const fallbacks = preferredPM === 'pnpm'
322
- ? ['npm', 'yarn', 'bun']
323
- : preferredPM === 'yarn'
324
- ? ['npm', 'pnpm', 'bun']
325
- : ['pnpm', 'yarn', 'bun'];
326
- for (const pm of fallbacks) {
327
- try {
328
- execSync(`${pm} install`, { stdio: 'inherit' });
329
- installed = true;
330
- installSpinner.stop(`✅ Dependencies installed with ${pm}!`);
331
- break;
332
- }
333
- catch {
334
- installSpinner.message(`❌ ${pm} install failed.`);
335
- }
336
- }
337
- if (!installed) {
338
- installSpinner.stop('❌ Failed to install dependencies automatically.');
339
- log.error(' Please run "pnpm install", "npm install", "yarn install", or "bun install" manually.');
340
- }
341
- }
342
- // Remove any existing global nextjs-cms-kit installation before installing with preferred PM
343
- log.step('Ensuring clean nextjs-cms-kit global installation...');
344
- const globalRemoveCommands = {
345
- pnpm: 'pnpm remove -g nextjs-cms-kit',
346
- npm: 'npm uninstall -g nextjs-cms-kit',
347
- yarn: 'yarn global remove nextjs-cms-kit',
348
- bun: 'bun remove -g nextjs-cms-kit',
349
- };
350
- // Try to remove from all package managers to ensure clean state
351
- const allPMs = ['pnpm', 'npm', 'yarn', 'bun'];
352
- for (const pm of allPMs) {
353
- try {
354
- execSync(globalRemoveCommands[pm], { stdio: 'pipe' });
355
- }
356
- catch {
357
- // Ignore errors - package might not be installed with this PM
358
- }
359
- }
360
- // Install nextjs-cms-kit globally for setup
361
- log.step('Installing nextjs-cms-kit globally...');
362
231
  try {
363
- const globalInstallCommands = {
364
- pnpm: 'pnpm add -g nextjs-cms-kit',
365
- npm: 'npm install -g nextjs-cms-kit',
366
- yarn: 'yarn global add nextjs-cms-kit',
367
- bun: 'bun add -g nextjs-cms-kit',
368
- };
369
- execSync(globalInstallCommands[preferredPM], { stdio: 'inherit' });
370
- log.step('nextjs-cms-kit installed globally');
232
+ log.step(`Using ${chalk.green(preferredPM)} as the package manager...`);
233
+ // Install dependencies first
234
+ await installDependencies({ projectDir: options.targetDir });
235
+ installed = true;
236
+ // Then remove any existing global package
237
+ await removeGlobalPackage('nextjs-cms-kit');
238
+ // Install the global package
239
+ await installGlobalPackage('nextjs-cms-kit', packageJson.version);
240
+ // Finally run CMS setup
241
+ await runCmsSetup({ dir: options.targetDir, installed, preferredPM });
371
242
  }
372
243
  catch (error) {
373
- log.warn('Could not install nextjs-cms-kit globally.');
374
- log.error(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
375
- }
376
- // Run CMS setup
377
- const setupSpinner = spinner();
378
- setupSpinner.start('Running CMS setup...');
379
- try {
380
- execSync('nextjs-cms-kit --dev setup', { stdio: 'inherit' });
381
- setupSpinner.stop('✅ CMS setup completed successfully!');
382
- }
383
- catch (error) {
384
- setupSpinner.stop('⚠️ CMS setup failed. You may need to run it manually:');
385
- log.warn(' nextjs-cms-kit --dev setup');
386
- log.error(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
244
+ log.error(`❌ Failed to complete project setup: ${error instanceof Error ? error.message : 'Unknown error'}`);
245
+ process.exit(1);
387
246
  }
388
- log.success('\n🎉 Your NextJS CMS project has been created successfully!');
389
- log.message('\nNext steps:');
390
- log.message(` cd ${options.projectName}`);
391
- log.message(installed
392
- ? ` ${preferredPM} dev # or: npm run dev`
393
- : ` ${preferredPM} install && ${preferredPM} dev # or: npm install && npm run dev`);
394
- log.message('\nHappy coding! 🚀');
395
247
  }
396
248
  catch (error) {
397
249
  log.error(`❌ Failed to create project: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -0,0 +1,6 @@
1
+ export declare const runCmsSetup: ({ dir, installed, preferredPM, }: {
2
+ dir: string;
3
+ installed: boolean;
4
+ preferredPM: string;
5
+ }) => Promise<void>;
6
+ //# sourceMappingURL=cms-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-setup.d.ts","sourceRoot":"","sources":["../../src/lib/cms-setup.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,WAAW,GAAU,kCAI/B;IACC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACtB,kBAcA,CAAA"}
@@ -0,0 +1,30 @@
1
+ import chalk from 'chalk';
2
+ import { logger } from './logger.js';
3
+ import { execWithoutSpinner } from './exec-utils.js';
4
+ import { log } from '@clack/prompts';
5
+ const runCmsSetupCommand = async (projectDir) => {
6
+ // For all package managers, use inherit for stdout/stderr to avoid hanging issues
7
+ // The command will show its own output and the spinner will just indicate progress
8
+ await execWithoutSpinner('nextjs-cms-kit', ['--dev', 'setup'], {
9
+ cwd: projectDir,
10
+ stdout: 'inherit',
11
+ stderr: 'inherit',
12
+ });
13
+ return null;
14
+ };
15
+ export const runCmsSetup = async ({ dir, installed, preferredPM, }) => {
16
+ log.info(chalk.cyan('Running CMS setup...'));
17
+ try {
18
+ await runCmsSetupCommand(dir);
19
+ logger.success('\n🎉 Your NextJS CMS project has been created successfully!');
20
+ logger.message('\nNext steps:');
21
+ logger.message(` cd ${dir}`);
22
+ logger.message(installed ? ` ${preferredPM} dev` : ` ${preferredPM} install && ${preferredPM} dev`);
23
+ }
24
+ catch (error) {
25
+ logger.error('⚠️ CMS setup failed. You may need to run it manually:');
26
+ logger.warn(' nextjs-cms-kit --dev setup');
27
+ logger.error(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
28
+ throw error;
29
+ }
30
+ };
@@ -0,0 +1,28 @@
1
+ import type { Ora } from 'ora';
2
+ export interface ExecWithSpinnerOptions {
3
+ command: string;
4
+ args: string[];
5
+ cwd?: string;
6
+ stdout?: 'pipe' | 'ignore' | 'inherit';
7
+ stderr?: 'pipe' | 'ignore' | 'inherit';
8
+ spinnerText?: string;
9
+ onDataHandle?: (spinner: Ora) => (data: Buffer) => void;
10
+ onStderrHandle?: (data: Buffer) => void;
11
+ checkExitCode?: boolean;
12
+ }
13
+ /**
14
+ * Execute a command with a spinner, handling different stdio configurations
15
+ * and optional data handlers for progress updates.
16
+ */
17
+ export declare const execWithSpinner: (options: ExecWithSpinnerOptions) => Promise<Ora | null>;
18
+ /**
19
+ * Execute a command without a spinner (for cases where we want direct output)
20
+ */
21
+ export declare const execWithoutSpinner: (command: string, args: string[], options?: {
22
+ cwd?: string;
23
+ stdout?: "pipe" | "ignore" | "inherit";
24
+ stderr?: "pipe" | "ignore" | "inherit";
25
+ reject?: boolean;
26
+ env?: Record<string, string>;
27
+ }) => Promise<void>;
28
+ //# sourceMappingURL=exec-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-utils.d.ts","sourceRoot":"","sources":["../../src/lib/exec-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAE9B,MAAM,WAAW,sBAAsB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACvD,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,aAAa,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,sBAAsB,KAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAsDzF,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC3B,SAAS,MAAM,EACf,MAAM,MAAM,EAAE,EACd,UAAS;IACL,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC1B,KACP,OAAO,CAAC,IAAI,CAUd,CAAA"}
@@ -0,0 +1,61 @@
1
+ import { execa } from 'execa';
2
+ import ora from 'ora';
3
+ /**
4
+ * Execute a command with a spinner, handling different stdio configurations
5
+ * and optional data handlers for progress updates.
6
+ */
7
+ export const execWithSpinner = async (options) => {
8
+ const { command, args, cwd, stdout = 'pipe', stderr = 'pipe', spinnerText = `Running ${command}...`, onDataHandle, onStderrHandle, checkExitCode = false, } = options;
9
+ const spinner = ora(spinnerText).start();
10
+ const subprocess = execa(command, args, {
11
+ cwd,
12
+ stdout,
13
+ stderr,
14
+ reject: false, // Don't reject on non-zero exit codes - we handle it manually
15
+ });
16
+ let stderrOutput = '';
17
+ await new Promise((res, rej) => {
18
+ if (onDataHandle) {
19
+ subprocess.stdout?.on('data', onDataHandle(spinner));
20
+ }
21
+ if (onStderrHandle) {
22
+ subprocess.stderr?.on('data', (data) => {
23
+ onStderrHandle(data);
24
+ });
25
+ }
26
+ else if (stderr === 'pipe') {
27
+ // Capture stderr to check for specific errors
28
+ subprocess.stderr?.on('data', (data) => {
29
+ stderrOutput += data.toString();
30
+ });
31
+ }
32
+ void subprocess.on('error', (e) => rej(e));
33
+ void subprocess.on('close', (code) => {
34
+ if (checkExitCode && code !== 0) {
35
+ const error = new Error(`${command} exited with code ${code}`);
36
+ if (stderrOutput) {
37
+ ;
38
+ error.stderr = stderrOutput;
39
+ }
40
+ rej(error);
41
+ }
42
+ else {
43
+ res();
44
+ }
45
+ });
46
+ });
47
+ return spinner;
48
+ };
49
+ /**
50
+ * Execute a command without a spinner (for cases where we want direct output)
51
+ */
52
+ export const execWithoutSpinner = async (command, args, options = {}) => {
53
+ const { cwd, stdout, stderr, reject = true, env } = options;
54
+ await execa(command, args, {
55
+ cwd,
56
+ stdout,
57
+ stderr,
58
+ reject,
59
+ env: env ? { ...process.env, ...env } : undefined,
60
+ });
61
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Remove a package globally from all package managers (to ensure clean state)
3
+ * Silent - spinner shown but no output, no errors, no success messages
4
+ */
5
+ export declare const removeGlobalPackage: (packageName: string) => Promise<void>;
6
+ /**
7
+ * Install a package globally using the detected package manager
8
+ */
9
+ export declare const installGlobalPackage: (packageName: string, version?: string) => Promise<void>;
10
+ //# sourceMappingURL=global-package.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-package.d.ts","sourceRoot":"","sources":["../../src/lib/global-package.ts"],"names":[],"mappings":"AAyIA;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,IAAI,CAc3E,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAU,aAAa,MAAM,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,IAAI,CAsB9F,CAAA"}
@@ -0,0 +1,158 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { detectPackageManager } from './utils.js';
4
+ import { execWithSpinner, execWithoutSpinner } from './exec-utils.js';
5
+ import { log } from '@clack/prompts';
6
+ const getGlobalRemoveCommand = (pkgManager, packageName) => {
7
+ switch (pkgManager) {
8
+ case 'pnpm':
9
+ return ['pnpm', ['remove', '-g', packageName]];
10
+ case 'npm':
11
+ return ['npm', ['uninstall', '-g', packageName]];
12
+ case 'yarn':
13
+ return ['yarn', ['global', 'remove', packageName]];
14
+ case 'bun':
15
+ return ['bun', ['remove', '-g', packageName]];
16
+ }
17
+ };
18
+ const getGlobalInstallCommand = (pkgManager, packageName, version) => {
19
+ const packageSpec = version ? `${packageName}@${version}` : packageName;
20
+ switch (pkgManager) {
21
+ case 'pnpm':
22
+ return ['pnpm', ['add', '-g', packageSpec]];
23
+ case 'npm':
24
+ return ['npm', ['install', '-g', packageSpec]];
25
+ case 'yarn':
26
+ return ['yarn', ['global', 'add', packageSpec]];
27
+ case 'bun':
28
+ return ['bun', ['add', '-g', packageSpec]];
29
+ }
30
+ };
31
+ const runGlobalRemoveCommand = async (pkgManager, packageName) => {
32
+ const [command, args] = getGlobalRemoveCommand(pkgManager, packageName);
33
+ // Silent execution with spinner - no output, no errors that quit
34
+ let stderrOutput = '';
35
+ let spinner = await execWithSpinner({
36
+ command,
37
+ args,
38
+ spinnerText: `Removing ${packageName} globally...`,
39
+ stdout: 'ignore',
40
+ stderr: 'pipe', // Capture stderr to detect path length errors
41
+ onStderrHandle: (data) => {
42
+ stderrOutput += data.toString();
43
+ },
44
+ checkExitCode: false, // Don't throw on errors - we want to silently ignore failures
45
+ });
46
+ // Check if we got the pnpm path length error
47
+ if (pkgManager === 'pnpm' && stderrOutput.toUpperCase().includes('PNPM_VIRTUAL_STORE_DIR_MAX_LENGTH_DIFF')) {
48
+ // Stop the current spinner before starting the fix
49
+ spinner?.stop();
50
+ // Run pnpm install -g to fix the issue (silently)
51
+ spinner = await execWithSpinner({
52
+ command: 'pnpm',
53
+ args: ['install', '-g', '--yes'],
54
+ stdout: 'ignore',
55
+ stderr: 'ignore',
56
+ checkExitCode: false,
57
+ });
58
+ spinner?.stop();
59
+ // Retry the remove command
60
+ spinner = await execWithSpinner({
61
+ command,
62
+ args,
63
+ spinnerText: `Removing ${packageName} globally...`,
64
+ stdout: 'ignore',
65
+ stderr: 'ignore',
66
+ checkExitCode: false,
67
+ });
68
+ }
69
+ return spinner;
70
+ };
71
+ const runGlobalInstallCommand = async (pkgManager, packageName, version) => {
72
+ const [command, args] = getGlobalInstallCommand(pkgManager, packageName, version);
73
+ switch (pkgManager) {
74
+ // When using npm, inherit the stderr stream so that the progress bar is shown
75
+ case 'npm':
76
+ await execWithoutSpinner(command, args, {
77
+ stdout: 'inherit',
78
+ stderr: 'inherit',
79
+ });
80
+ return null;
81
+ // When using yarn or pnpm, use the stdout stream and ora spinner to show the progress
82
+ case 'pnpm':
83
+ return execWithSpinner({
84
+ command,
85
+ args,
86
+ spinnerText: `Installing ${packageName} globally...`,
87
+ onDataHandle: (spinner) => (data) => {
88
+ const text = data.toString();
89
+ if (text.includes('Progress')) {
90
+ spinner.text = text.includes('|') ? (text.split(' | ')[1] ?? '') : text;
91
+ }
92
+ },
93
+ });
94
+ case 'yarn':
95
+ return execWithSpinner({
96
+ command,
97
+ args,
98
+ spinnerText: `Installing ${packageName} globally...`,
99
+ onDataHandle: (spinner) => (data) => {
100
+ spinner.text = data.toString();
101
+ },
102
+ });
103
+ // When using bun, the stdout stream is ignored and the spinner is shown
104
+ case 'bun':
105
+ return execWithSpinner({
106
+ command,
107
+ args,
108
+ spinnerText: `Installing ${packageName} globally...`,
109
+ stdout: 'ignore',
110
+ });
111
+ }
112
+ };
113
+ /**
114
+ * Remove a package globally from all package managers (to ensure clean state)
115
+ * Silent - spinner shown but no output, no errors, no success messages
116
+ */
117
+ export const removeGlobalPackage = async (packageName) => {
118
+ const allPMs = ['pnpm', 'npm', 'yarn', 'bun'];
119
+ for (const pm of allPMs) {
120
+ try {
121
+ const spinner = await runGlobalRemoveCommand(pm, packageName);
122
+ // Stop spinner silently without showing success/fail
123
+ if (spinner) {
124
+ spinner.stop();
125
+ }
126
+ }
127
+ catch {
128
+ // Silently ignore all errors - package might not be installed with this PM
129
+ }
130
+ }
131
+ };
132
+ /**
133
+ * Install a package globally using the detected package manager
134
+ */
135
+ export const installGlobalPackage = async (packageName, version) => {
136
+ log.info(chalk.cyan(`Installing ${packageName} globally...`));
137
+ const pkgManager = detectPackageManager();
138
+ try {
139
+ const installSpinner = await runGlobalInstallCommand(pkgManager, packageName, version);
140
+ // If the spinner was used to show the progress, use succeed method on it
141
+ // If not, create a new spinner for success message
142
+ if (installSpinner) {
143
+ installSpinner.stop();
144
+ // log.message(chalk.green(`Successfully installed ${packageName} globally!`))
145
+ }
146
+ else {
147
+ // log.message(chalk.green(`Successfully installed ${packageName} globally!`))
148
+ }
149
+ }
150
+ catch (error) {
151
+ // Stop any existing spinner and show failure
152
+ ora().fail(chalk.red(`Failed to install ${packageName} globally`));
153
+ log.warn(`⚠️ Could not install ${packageName} globally.`);
154
+ const [command, args] = getGlobalInstallCommand(pkgManager, packageName, version);
155
+ log.message(` - Please run ${chalk.gray(`${command} ${args.join(' ')}`)} manually.`);
156
+ throw error;
157
+ }
158
+ };
@@ -0,0 +1,4 @@
1
+ export declare const installDependencies: ({ projectDir }: {
2
+ projectDir: string;
3
+ }) => Promise<void>;
4
+ //# sourceMappingURL=install-deps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-deps.d.ts","sourceRoot":"","sources":["../../src/lib/install-deps.ts"],"names":[],"mappings":"AA4DA,eAAO,MAAM,mBAAmB,GAAU,gBAAgB;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,kBA6D/E,CAAA"}