@cellajs/create-cella 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/create.ts CHANGED
@@ -1,193 +1,114 @@
1
- import { mkdir } from 'node:fs/promises';
2
1
  import { existsSync } from 'node:fs';
3
- import { join, relative } from 'node:path';
4
- import colors from 'picocolors';
2
+ import { cp, mkdir } from 'node:fs/promises';
3
+ import { join, relative, resolve } from 'node:path';
5
4
  import { downloadTemplate } from 'giget';
6
- import yoctoSpinner from 'yocto-spinner';
7
-
8
- import { TEMPLATE_URL } from './constants.ts';
9
-
10
- import { install, generate } from './utils/run-package-manager-command.ts';
11
- import { cleanTemplate } from './utils/clean-template.ts';
12
- import { runGitCommand } from './utils/run-git-command.ts';
13
- import { addRemote } from './add-remote.ts';
14
-
15
- interface CreateOptions {
16
- projectName: string;
17
- targetFolder: string;
18
- newBranchName?: string | null;
19
- skipInstall: boolean;
20
- skipGit: boolean;
21
- skipClean: boolean;
22
- skipGenerate: boolean;
23
- packageManager: string;
5
+ import { addRemote } from '#/add-remote';
6
+ import { getPortEdits, TEMPLATE_URL } from '#/constants';
7
+ import { type CreateOptions, showSuccess } from '#/modules/cli';
8
+ import { cleanTemplate } from '#/utils/clean-template';
9
+ import { gitAddAll, gitBranch, gitCheckout, gitCommit, gitInit } from '#/utils/git';
10
+ import { createProgress } from '#/utils/progress';
11
+ import { generate, install } from '#/utils/run-package-manager-command';
12
+
13
+ /** Check if a path is a local directory */
14
+ function isLocalPath(path: string): boolean {
15
+ return path.startsWith('/') || path.startsWith('./') || path.startsWith('../');
24
16
  }
25
17
 
26
18
  export async function create({
27
19
  projectName,
28
20
  targetFolder,
29
21
  newBranchName,
30
- skipInstall,
31
- skipGit,
32
- skipClean,
33
- skipGenerate,
34
22
  packageManager,
23
+ templateUrl,
24
+ portOffset = 0,
25
+ silent = false,
35
26
  }: CreateOptions): Promise<void> {
36
27
  // Save the original working directory
37
28
  const originalCwd = process.cwd();
38
29
 
39
- console.info();
40
-
41
- // Create the target folder if it doesn't exist
42
- const createFolderSpinner = yoctoSpinner({
43
- text: 'Creating project folder',
44
- }).start();
45
-
46
- await mkdir(targetFolder, { recursive: true });
47
- process.chdir(targetFolder);
48
-
49
- createFolderSpinner.success('Project folder created');
50
-
51
- // Download the template from the specified URL
52
- const downloadSpinner = yoctoSpinner({
53
- text: 'Downloading `cella` template',
54
- }).start();
55
-
56
- await downloadTemplate(TEMPLATE_URL, {
57
- cwd: process.cwd(),
58
- dir: targetFolder,
59
- force: true,
60
- provider: 'github',
61
- });
62
-
63
- downloadSpinner.success('`cella` template downloaded');
64
-
65
- // Clean the template if the skipClean flag is not set
66
- if (!skipClean) {
67
- const cleanSpinner = yoctoSpinner({
68
- text: 'cleaning `cella` template',
69
- }).start();
30
+ // Use custom template or default
31
+ const template = templateUrl || TEMPLATE_URL;
32
+ const isLocalTemplate = templateUrl && isLocalPath(templateUrl);
33
+
34
+ const progress = createProgress('creating project', silent);
35
+
36
+ try {
37
+ // Create the target folder
38
+ progress.step('creating project folder');
39
+ await mkdir(targetFolder, { recursive: true });
40
+ process.chdir(targetFolder);
41
+
42
+ // Download or copy the template
43
+ if (isLocalTemplate) {
44
+ progress.step('copying local template');
45
+ const sourcePath = resolve(originalCwd, templateUrl);
46
+
47
+ // Check if target is inside source (would cause EINVAL)
48
+ if (targetFolder.startsWith(sourcePath)) {
49
+ throw new Error(
50
+ `Cannot create project inside the template directory.\n` +
51
+ ` Run from outside: cd ~ && pnpm create @cellajs/cella ${projectName} --template ${templateUrl}`,
52
+ );
53
+ }
70
54
 
71
- try {
72
- await cleanTemplate({
73
- targetFolder,
74
- projectName,
55
+ await cp(sourcePath, targetFolder, {
56
+ recursive: true,
57
+ filter: (src) => !src.includes('node_modules') && !src.includes('.git'),
58
+ });
59
+ } else {
60
+ progress.step('downloading cella template');
61
+ await downloadTemplate(template, {
62
+ cwd: process.cwd(),
63
+ dir: targetFolder,
64
+ force: true,
65
+ provider: 'github',
75
66
  });
76
- cleanSpinner.success('`cella` template cleaned');
77
- } catch (e) {
78
- console.error(e);
79
- cleanSpinner.error('Failed to clean `cella` template');
80
- process.exit(1);
81
67
  }
82
- } else {
83
- console.info(`${colors.yellow('⚠')} --skip-clean > Skip cleaning \`cella\` template`);
84
- }
85
68
 
86
- // Install dependencies if the skipInstall flag is not set
87
- if (!skipInstall) {
88
- const installSpinner = yoctoSpinner({
89
- text: 'installing dependencies',
90
- }).start();
91
-
92
- try {
93
- await install(packageManager);
94
- installSpinner.success('Dependencies installed');
95
- } catch (e) {
96
- console.error(e);
97
- installSpinner.error('Failed to install dependencies');
98
- process.exit(1);
99
- }
100
- } else {
101
- console.info(`${colors.yellow('⚠')} --skip-install > Skip installing dependencies`);
102
- }
69
+ // Clean the template and apply port offsets
70
+ progress.step('cleaning template');
71
+ const extraEdits = getPortEdits(projectName, portOffset);
72
+ await cleanTemplate({ targetFolder, extraEdits });
103
73
 
104
- // Generate SQL files if the skipGenerate flag is not set
105
- if (!skipGenerate) {
106
- const generateSpinner = yoctoSpinner({
107
- text: 'generating SQL files',
108
- }).start();
109
-
110
- try {
111
- await generate(packageManager);
112
- generateSpinner.success('SQL files generated');
113
- } catch (e) {
114
- console.error(e);
115
- generateSpinner.error('Failed to generate SQL files');
116
- process.exit(1);
117
- }
118
- } else {
119
- console.info(`${colors.yellow('⚠')} --skip-generate > Skip generating SQL files`);
120
- }
74
+ // Install dependencies
75
+ progress.step('installing dependencies');
76
+ await install(packageManager);
121
77
 
122
- // Initialize Git repository if skipGit flag is not set
123
- if (!skipGit) {
124
- const gitSpinner = yoctoSpinner({
125
- text: 'initializing git repository',
126
- }).start();
78
+ // Generate SQL files
79
+ progress.step('generating migrations');
80
+ await generate(packageManager);
127
81
 
82
+ // Initialize git repository
128
83
  const gitFolderPath = join(targetFolder, '.git');
129
-
130
84
  if (!existsSync(gitFolderPath)) {
131
- try {
132
- // Run Git commands to initialize the repository and make the first commit
133
- await runGitCommand({ targetFolder, command: 'init' });
134
- await runGitCommand({ targetFolder, command: 'add .' });
135
- await runGitCommand({ targetFolder, command: 'commit -m "Initial commit"' });
136
-
137
- // If a new branch name is specified, create and checkout the branch
138
- if (newBranchName) {
139
- await runGitCommand({ targetFolder, command: `branch ${newBranchName}` });
140
- await runGitCommand({ targetFolder, command: `checkout ${newBranchName}` });
141
- gitSpinner.success(`Git repository initialized, initial commit created, and new branch ${newBranchName} created`);
142
- } else {
143
- gitSpinner.success('Git repository initialized and initial commit created');
144
- }
145
- } catch (e) {
146
- console.error(e);
147
- gitSpinner.error('Failed to initialize Git repository or create branch');
148
- process.exit(1);
85
+ progress.step('initializing git');
86
+ await gitInit(targetFolder);
87
+ await gitAddAll(targetFolder);
88
+ await gitCommit(targetFolder, 'Initial commit');
89
+
90
+ if (newBranchName) {
91
+ progress.step(`creating branch '${newBranchName}'`);
92
+ await gitBranch(targetFolder, newBranchName);
93
+ await gitCheckout(targetFolder, newBranchName);
149
94
  }
150
- } else {
151
- gitSpinner.warning('Git repository already initialized > Skip git init');
152
95
  }
153
- } else {
154
- console.info(`${colors.yellow('⚠')} --skip-git > Skip git init`);
155
- }
156
96
 
157
- // Add Cella as upstream remote
158
- await addRemote({ targetFolder });
97
+ // Add upstream remote
98
+ progress.step('adding upstream remote');
99
+ await addRemote({ targetFolder, silent: true });
159
100
 
160
- // Final success message indicating project creation
161
- console.info();
162
- console.info(`${colors.green('Success')} Created ${projectName} at ${targetFolder}`);
163
- console.info();
101
+ // Done
102
+ progress.done(`created ${projectName}`);
103
+ } catch (error) {
104
+ progress.fail(error instanceof Error ? error.message : String(error));
105
+ process.exit(1);
106
+ }
164
107
 
165
108
  // Check if the working directory needs to be changed
166
109
  const needsCd = originalCwd !== targetFolder;
167
110
  const relativePath = relative(originalCwd, targetFolder);
168
111
 
169
- if (needsCd) {
170
- // Calculate the relative path between the original working directory and the target folder
171
- console.info('now go to your project using:');
172
- console.info(colors.cyan(` cd ${relativePath}`)); // Adding './' to make it clear it's a relative path
173
- console.info();
174
- }
175
-
176
- console.info(`${needsCd ? 'then ' : ''}quick start using pglite with:`);
177
- console.info(colors.cyan(` ${packageManager} quick`));
178
- console.info();
179
-
180
- console.info('Already have docker installed? Then you can run a full setup:');
181
- console.info(colors.cyan(` ${packageManager} docker`));
182
- console.info(colors.cyan(` ${packageManager} dev`));
183
- console.info(colors.cyan(` ${packageManager} seed`));
184
- console.info();
185
-
186
- console.info(`Once running, you can sign in using:`);
187
- console.info(`email: ${colors.greenBright('admin-test@cellajs.com')}`);
188
- console.info(`password: ${colors.greenBright('12345678')}`);
189
- console.info();
190
- console.info(`For more info, check out: ${relativePath}/README.md`);
191
- console.info(`Enjoy building ${projectName} using cella! 🎉`);
192
- console.info();
112
+ // Display final success message
113
+ showSuccess(projectName, targetFolder, relativePath, needsCd, packageManager);
193
114
  }
@@ -0,0 +1,58 @@
1
+ import { basename, resolve } from 'node:path';
2
+ import { Command, InvalidArgumentError } from 'commander';
3
+
4
+ import { NAME, VERSION } from '#/constants';
5
+ import { validateProjectName } from '#/utils/validate-project-name';
6
+ import type { CLIConfig, CLIOptions } from './types';
7
+
8
+ // Initialize CLI variables
9
+ let directory: string | null = null;
10
+ let newBranchName: string | null = null;
11
+ const packageManager = 'pnpm';
12
+
13
+ /**
14
+ * Defines the root CLI command using Commander.
15
+ * This command accepts CLI options and validates user input.
16
+ */
17
+ export const command = new Command(NAME)
18
+ .version(VERSION, '-v, --version', `output the current version of ${NAME}`)
19
+ .argument('[directory]', 'the directory name for the new project')
20
+ .usage('[directory] [options]')
21
+ .helpOption('-h, --help', 'display this help message')
22
+ .option('--template <path>', 'use a custom template (local path or github:user/repo)')
23
+ .action((name: string) => {
24
+ if (typeof name === 'string') {
25
+ name = name.trim();
26
+ }
27
+
28
+ if (name) {
29
+ const validation = validateProjectName(basename(resolve(name)));
30
+
31
+ if (!validation.valid) {
32
+ throw new InvalidArgumentError(`Invalid project name: ${validation.problems?.[0] ?? 'unknown error'}`);
33
+ }
34
+
35
+ directory = name;
36
+ }
37
+ })
38
+ .parse();
39
+
40
+ // Gather the CLI options and arguments
41
+ const options: CLIOptions = command.opts<CLIOptions>();
42
+
43
+ /**
44
+ * Runs the CLI and returns the parsed configuration.
45
+ * This function parses command line arguments and returns the CLI config.
46
+ */
47
+ export function runCli(): CLIConfig {
48
+ return {
49
+ options,
50
+ args: command.args,
51
+ directory,
52
+ newBranchName,
53
+ packageManager,
54
+ };
55
+ }
56
+
57
+ // Export CLI configuration for direct import
58
+ export const cli: CLIConfig = runCli();
@@ -0,0 +1,62 @@
1
+ import pc from 'picocolors';
2
+
3
+ import { DESCRIPTION, DIVIDER, getHeaderLine } from '#/constants';
4
+
5
+ /** ASCII art logo for the CLI welcome screen. */
6
+ function showAscii(): void {
7
+ console.info(pc.cyan(' _ _ '));
8
+ console.info(pc.cyan('▒▓█████▓▒ ___ ___| | | __ _ '));
9
+ console.info(pc.cyan('▒▓█ █▓▒ / __/ _ \\ | |/ _` | '));
10
+ console.info(pc.cyan('▒▓█ █▓▒ | (_| __/ | | (_| | '));
11
+ console.info(pc.cyan('▒▓█████▓▒ \\___\\___|_|_|\\__,_| '));
12
+ }
13
+
14
+ /**
15
+ * Displays the compact CLI welcome header.
16
+ * @param templateVersion - The version of the cella template being used
17
+ */
18
+ export function showWelcome(templateVersion: string): void {
19
+ console.info();
20
+ showAscii();
21
+ console.info();
22
+ console.info(pc.dim(DESCRIPTION));
23
+ console.info();
24
+ console.info(getHeaderLine(templateVersion));
25
+ console.info(DIVIDER);
26
+ }
27
+
28
+ /**
29
+ * Displays the final success message after project creation.
30
+ */
31
+ export function showSuccess(
32
+ projectName: string,
33
+ _targetFolder: string,
34
+ relativePath: string,
35
+ needsCd: boolean,
36
+ packageManager: string,
37
+ ): void {
38
+ console.info(DIVIDER);
39
+ console.info();
40
+
41
+ // Navigation instruction
42
+ if (needsCd) {
43
+ console.info(`${pc.green('→')} cd ${pc.cyan(relativePath)}`);
44
+ console.info();
45
+ }
46
+
47
+ // Quick start options
48
+ console.info(`${pc.green('→')} ${pc.cyan(`${packageManager} quick`)} ${pc.gray('(pglite, no docker)')}`);
49
+ console.info();
50
+ console.info(pc.gray('or, for full setup:'));
51
+ console.info();
52
+ console.info(
53
+ `${pc.green('→')} ${pc.cyan(`${packageManager} docker`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} seed`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} dev`)}`,
54
+ );
55
+ console.info();
56
+
57
+ // Credentials
58
+ console.info(`sign in: ${pc.gray('admin-test@cellajs.com / 12345678')}`);
59
+ console.info();
60
+ console.info(`enjoy building ${pc.green(projectName)} with cella!`);
61
+ console.info();
62
+ }
@@ -0,0 +1,3 @@
1
+ export { cli, command, runCli } from './commands';
2
+ export { showSuccess, showWelcome } from './display';
3
+ export type { AddRemoteOptions, CLIConfig, CLIOptions, CreateOptions } from './types';
@@ -0,0 +1,35 @@
1
+ /** CLI options parsed from command line arguments */
2
+ export interface CLIOptions {
3
+ template?: string;
4
+ }
5
+
6
+ /** CLI configuration state */
7
+ export interface CLIConfig {
8
+ options: CLIOptions;
9
+ args: string[];
10
+ directory: string | null;
11
+ newBranchName: string | null;
12
+ packageManager: string;
13
+ }
14
+
15
+ /** Options for creating a new project */
16
+ export interface CreateOptions {
17
+ projectName: string;
18
+ targetFolder: string;
19
+ newBranchName?: string | null;
20
+ packageManager: string;
21
+ templateUrl?: string;
22
+ /** Port offset to avoid collisions with sibling forks (0 = default ports) */
23
+ portOffset?: number;
24
+ /** Suppress all output (for testing) */
25
+ silent?: boolean;
26
+ }
27
+
28
+ /** Options for adding a remote to the repository */
29
+ export interface AddRemoteOptions {
30
+ targetFolder: string;
31
+ remoteUrl?: string;
32
+ remoteName?: string;
33
+ /** If true, don't throw on failure */
34
+ silent?: boolean;
35
+ }
@@ -1,19 +1,19 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import colors from 'picocolors';
3
+ import pc from 'picocolors';
4
4
 
5
- import { TO_CLEAN, TO_REMOVE, TO_COPY, TO_EDIT } from '../constants.ts';
5
+ import { type FileEdit, TO_CLEAN, TO_COPY, TO_EDIT, TO_REMOVE } from '#/constants';
6
6
 
7
7
  /**
8
8
  * Cleans the specified template by removing designated folders and files.
9
- * @param params - Parameters containing the target folder and project name.
9
+ * @param params - Parameters containing the target folder and optional extra edits.
10
10
  */
11
11
  export async function cleanTemplate({
12
12
  targetFolder,
13
- projectName,
13
+ extraEdits = {},
14
14
  }: {
15
15
  targetFolder: string;
16
- projectName: string;
16
+ extraEdits?: Record<string, FileEdit[]>;
17
17
  }): Promise<void> {
18
18
  // Change the current working directory to targetFolder if not already set
19
19
  if (process.cwd() !== targetFolder) {
@@ -34,7 +34,7 @@ export async function cleanTemplate({
34
34
  TO_CLEAN.map((folderPath) => {
35
35
  const absolutePath = path.resolve(targetFolder, folderPath);
36
36
  return removeFolderContents(absolutePath);
37
- })
37
+ }),
38
38
  );
39
39
 
40
40
  // Remove specified files and folders
@@ -42,14 +42,21 @@ export async function cleanTemplate({
42
42
  TO_REMOVE.map((filePath) => {
43
43
  const absolutePath = path.resolve(targetFolder, filePath);
44
44
  return removeFileOrFolder(absolutePath);
45
- })
45
+ }),
46
46
  );
47
47
 
48
+ // Merge static edits with extra edits (e.g., port offsets)
49
+ const allEdits = { ...TO_EDIT };
50
+ for (const [filePath, edits] of Object.entries(extraEdits)) {
51
+ allEdits[filePath] = [...(allEdits[filePath] || []), ...edits];
52
+ }
53
+
48
54
  // Edit specific files
49
- await Promise.all(Object.entries(TO_EDIT).map(async ([filePath, edits]) => {
55
+ await Promise.all(
56
+ Object.entries(allEdits).map(async ([filePath, edits]) => {
50
57
  const absolutePath = path.resolve(targetFolder, filePath);
51
58
  await editFile(absolutePath, edits);
52
- })
59
+ }),
53
60
  );
54
61
 
55
62
  resolve();
@@ -81,7 +88,7 @@ export async function removeFolderContents(folderPath: string): Promise<void> {
81
88
  // If it's a file, remove it
82
89
  await fs.rm(filePath);
83
90
  }
84
- })
91
+ }),
85
92
  );
86
93
  }
87
94
 
@@ -110,7 +117,7 @@ export async function copyFile(src: string, dest: string): Promise<void> {
110
117
  await fs.copyFile(src, dest);
111
118
  } catch (err: any) {
112
119
  if (err.code === 'ENOENT') {
113
- console.info(`\n${colors.yellow('⚠')} Source file "${src}" does not exist > Skip copy`);
120
+ console.info(`\n${pc.yellow('⚠')} Source file "${src}" does not exist > Skip copy`);
114
121
  } else {
115
122
  throw err;
116
123
  }
@@ -122,7 +129,10 @@ export async function copyFile(src: string, dest: string): Promise<void> {
122
129
  * @param filePath - The path of the file to edit.
123
130
  * @param edits - The list of edits to apply.
124
131
  */
125
- export async function editFile(filePath: string, edits: Array<{regexMatch: RegExp; replaceWith: string }>): Promise<void> {
132
+ export async function editFile(
133
+ filePath: string,
134
+ edits: Array<{ regexMatch: RegExp; replaceWith: string }>,
135
+ ): Promise<void> {
126
136
  try {
127
137
  await fs.access(filePath);
128
138
 
@@ -139,12 +149,11 @@ export async function editFile(filePath: string, edits: Array<{regexMatch: RegEx
139
149
  if (fileContent !== updatedContent) {
140
150
  await fs.writeFile(filePath, updatedContent, 'utf8');
141
151
  }
142
-
143
152
  } catch (err: any) {
144
153
  if (err.code === 'ENOENT') {
145
- console.info(`\n${colors.yellow('⚠')} Source file "${filePath}" does not exist > Skip edit`);
154
+ console.info(`\n${pc.yellow('⚠')} Source file "${filePath}" does not exist > Skip edit`);
146
155
  } else {
147
156
  throw err;
148
157
  }
149
158
  }
150
- }
159
+ }
@@ -0,0 +1,57 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+
4
+ interface UsedPorts {
5
+ project: string;
6
+ frontend: number;
7
+ backend: number;
8
+ offset: number;
9
+ }
10
+
11
+ /**
12
+ * Scan sibling directories for existing cella forks and extract their dev ports.
13
+ * Looks for `shared/development-config.ts` to identify cella-based projects.
14
+ */
15
+ export async function detectUsedPorts(targetFolder: string): Promise<UsedPorts[]> {
16
+ const parentDir = dirname(targetFolder);
17
+ const used: UsedPorts[] = [];
18
+
19
+ let siblings: string[];
20
+ try {
21
+ siblings = await readdir(parentDir);
22
+ } catch {
23
+ return used;
24
+ }
25
+
26
+ for (const name of siblings) {
27
+ const configPath = join(parentDir, name, 'shared/development-config.ts');
28
+ try {
29
+ const content = await readFile(configPath, 'utf8');
30
+ const feMatch = content.match(/frontendUrl:\s*'http:\/\/localhost:(\d+)'/);
31
+ const beMatch = content.match(/backendUrl:\s*'http:\/\/localhost:(\d+)'/);
32
+ if (feMatch && beMatch) {
33
+ const frontend = Number(feMatch[1]);
34
+ const backend = Number(beMatch[1]);
35
+ used.push({
36
+ project: name,
37
+ frontend,
38
+ backend,
39
+ offset: frontend - 3000,
40
+ });
41
+ }
42
+ } catch {
43
+ // Not a cella fork, skip
44
+ }
45
+ }
46
+
47
+ return used;
48
+ }
49
+
50
+ /** Find the next available offset (in steps of 10) that doesn't collide with existing forks. */
51
+ export function findNextOffset(usedPorts: UsedPorts[]): number {
52
+ const usedOffsets = new Set(usedPorts.map((p) => p.offset));
53
+ for (let offset = 0; offset <= 490; offset += 10) {
54
+ if (!usedOffsets.has(offset)) return offset;
55
+ }
56
+ return 0;
57
+ }
@@ -3,15 +3,15 @@ import axios from 'axios';
3
3
  /**
4
4
  * Retrieves the version from the package.json file of a GitHub repository.
5
5
  * If the package.json file is not found or an error occurs, it returns 'unknown'.
6
- *
6
+ *
7
7
  * @param repositoryUrl {string} - The GitHub repository URL in the format 'github:user/repo'.
8
8
  * @param branch {string} - The branch name (defaults to 'main').
9
9
  * @returns {Promise<string>} - The version from the package.json file.
10
10
  */
11
- export async function extractPackageJsonVersionFromUri(repositoryUrl: string, branch: string = 'main'): Promise<string> {
11
+ export async function extractPackageJsonVersionFromUri(repositoryUrl: string, branch = 'main'): Promise<string> {
12
12
  // Extract owner and repo from the URL
13
13
  const [owner, repo] = repositoryUrl.replace('github:', '').split('/');
14
-
14
+
15
15
  // Construct the URL for the package.json file in the provided branch
16
16
  const packageJsonUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/package.json`;
17
17
 
@@ -19,11 +19,11 @@ export async function extractPackageJsonVersionFromUri(repositoryUrl: string, br
19
19
  // Fetch the package.json file
20
20
  const response = await axios.get(packageJsonUrl);
21
21
  const packageJson = response.data;
22
-
22
+
23
23
  // Return the version from the package.json, or 'unknown' if not found
24
24
  return packageJson.version || 'unknown';
25
25
  } catch (error) {
26
26
  // If there's an error (file not found, etc.), return 'unknown'
27
27
  return 'unknown';
28
28
  }
29
- }
29
+ }