create-vitnode-app 1.2.0-canary.3 → 1.2.0-canary.31

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 (42) hide show
  1. package/README.md +6 -0
  2. package/copy-of-vitnode-app/docker/docker-compose.yml +20 -0
  3. package/copy-of-vitnode-app/eslint/.prettierrc.mjs +1 -1
  4. package/copy-of-vitnode-app/eslint/eslint.config.mjs +1 -1
  5. package/copy-of-vitnode-app/root/.env.example +10 -0
  6. package/copy-of-vitnode-app/root/.gitignore_template +28 -0
  7. package/copy-of-vitnode-app/root/.prettierrc.mjs +11 -0
  8. package/copy-of-vitnode-app/root/.vscode/settings.json +6 -0
  9. package/copy-of-vitnode-app/root/README.md +48 -0
  10. package/copy-of-vitnode-app/root/global.d.ts +9 -0
  11. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/layout.tsx +6 -1
  12. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/page.tsx +7 -15
  13. package/copy-of-vitnode-app/root/src/app/[locale]/admin/(auth)/layout.tsx +8 -5
  14. package/copy-of-vitnode-app/root/src/app/[locale]/layout.tsx +13 -8
  15. package/copy-of-vitnode-app/root/src/app/api/[...route]/route.ts +3 -2
  16. package/copy-of-vitnode-app/root/src/app/global-error.tsx +3 -3
  17. package/copy-of-vitnode-app/root/src/app/{globals.css → global.css} +61 -53
  18. package/copy-of-vitnode-app/root/src/app/layout.tsx +1 -1
  19. package/copy-of-vitnode-app/root/src/middleware.ts +24 -0
  20. package/copy-of-vitnode-app/root/src/vitnode.api.config.ts +9 -0
  21. package/copy-of-vitnode-app/root/src/vitnode.config.ts +32 -0
  22. package/copy-of-vitnode-app/root/tsconfig.json +2 -1
  23. package/dist/src/create/create-package-json.js +74 -0
  24. package/dist/src/create/create-vitnode.js +70 -0
  25. package/dist/src/helpers/get-available-package-managers.js +24 -0
  26. package/dist/src/helpers/get-package-json.js +2 -0
  27. package/dist/src/helpers/init-vitnode.js +15 -0
  28. package/dist/src/helpers/install-dependencies.js +67 -0
  29. package/dist/src/helpers/is-folder-empty.js +49 -0
  30. package/dist/src/helpers/is-online.js +38 -0
  31. package/dist/src/helpers/is-writeable.js +11 -0
  32. package/dist/src/helpers/packages-json.js +1 -0
  33. package/dist/src/helpers/validate-pkg.js +14 -0
  34. package/dist/src/index.js +65 -0
  35. package/dist/src/plugin/index.js +18 -0
  36. package/dist/src/prepare/prepare.js +16 -0
  37. package/dist/src/questions.js +52 -0
  38. package/dist/src/validation.js +40 -0
  39. package/dist/tsconfig.tsbuildinfo +1 -0
  40. package/package.json +9 -9
  41. package/copy-of-vitnode-app/root/src/app/[locale]/admin/not-found.tsx +0 -5
  42. package/copy-of-vitnode-app/root/src/app/not-found.tsx +0 -13
@@ -0,0 +1,70 @@
1
+ import { existsSync } from 'fs';
2
+ import { copyFile, cp, mkdir, readFile, rename, writeFile } from 'fs/promises';
3
+ import ora from 'ora';
4
+ import { dirname, join } from 'path';
5
+ import color from 'picocolors';
6
+ import { fileURLToPath } from 'url';
7
+ import { generateMigrationsVitnode, initFilesVitnode, } from '../helpers/init-vitnode.js';
8
+ import { installDependencies } from '../helpers/install-dependencies.js';
9
+ import { isFolderEmpty } from '../helpers/is-folder-empty.js';
10
+ import { createPackageJSON } from './create-package-json.js';
11
+ export const createVitNode = async ({ root, appName, packageManager, eslint, install, docker, }) => {
12
+ const spinner = ora(`Creating a new VitNode app in ${color.green(root)}. Using ${color.green(packageManager)}...`).start();
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const templatePath = join(__dirname, '..', '..', '..', 'copy-of-vitnode-app');
16
+ if (!existsSync(templatePath)) {
17
+ spinner.fail(`\n${color.red('Error!')} Template path ${color.cyan(templatePath)} does not exist.`);
18
+ process.exit(1);
19
+ }
20
+ await mkdir(root, { recursive: true });
21
+ if (!isFolderEmpty(root, appName)) {
22
+ process.exit(1);
23
+ }
24
+ spinner.text = 'Copying files...';
25
+ await cp(join(templatePath, 'root'), root, {
26
+ recursive: true,
27
+ });
28
+ if (eslint) {
29
+ spinner.text = 'Copying eslint files...';
30
+ await cp(join(templatePath, 'eslint'), root, {
31
+ recursive: true,
32
+ });
33
+ }
34
+ spinner.text = 'Renaming special files...';
35
+ await rename(join(root, '.gitignore_template'), join(root, '.gitignore'));
36
+ spinner.text = 'Creating package.json...';
37
+ await createPackageJSON({
38
+ root,
39
+ appName,
40
+ packageManager,
41
+ eslint,
42
+ docker,
43
+ });
44
+ if (docker) {
45
+ spinner.text = 'Copying docker files...';
46
+ await copyFile(join(templatePath, 'docker', 'docker-compose.yml'), join(root, 'docker-compose.yml'));
47
+ const dockerComposePath = join(root, 'docker-compose.yml');
48
+ const dockerComposeContent = await readFile(dockerComposePath, 'utf-8');
49
+ const updatedContent = dockerComposeContent.replace(/vitnode_postgres_dev/g, `${appName}_vitnode_postgres_dev`);
50
+ await writeFile(dockerComposePath, updatedContent);
51
+ }
52
+ if (install) {
53
+ spinner.text = 'Installing dependencies...';
54
+ await installDependencies({
55
+ packageManager,
56
+ cwd: root,
57
+ });
58
+ spinner.text = 'Initializing VitNode files...';
59
+ initFilesVitnode({
60
+ packageManager,
61
+ cwd: root,
62
+ });
63
+ spinner.text = 'Generating migrations...';
64
+ generateMigrationsVitnode({
65
+ packageManager,
66
+ cwd: root,
67
+ });
68
+ }
69
+ spinner.succeed(`${color.green('Success!')} Created ${color.cyan(appName)} at ${color.cyan(root)}`);
70
+ };
@@ -0,0 +1,24 @@
1
+ import { exec } from 'child_process';
2
+ export const execShellCommand = async (cmd) => {
3
+ return new Promise(resolve => {
4
+ exec(cmd, (error, stdout, stderr) => {
5
+ if (error) {
6
+ resolve(undefined);
7
+ }
8
+ const result = stdout ? stdout : stderr;
9
+ resolve(result.replace(/\s+/g, ''));
10
+ });
11
+ });
12
+ };
13
+ export const getAvailablePackageManagers = async () => {
14
+ const [npm, pnpm, bun] = await Promise.all([
15
+ execShellCommand('npm --version'),
16
+ execShellCommand('pnpm --version'),
17
+ execShellCommand('bun --version'),
18
+ ]);
19
+ return {
20
+ pnpm,
21
+ npm,
22
+ bun,
23
+ };
24
+ };
@@ -0,0 +1,2 @@
1
+ import { readFileSync } from 'fs';
2
+ export const packageJson = JSON.parse(readFileSync(new URL('../../../package.json', import.meta.url), 'utf-8'));
@@ -0,0 +1,15 @@
1
+ import { spawn } from 'child_process';
2
+ export const initFilesVitnode = ({ packageManager: pm, cwd, }) => {
3
+ const packageManager = pm.split('@')[0];
4
+ const args = ['vitnode', 'prepare-plugins'];
5
+ spawn(packageManager, args, {
6
+ cwd,
7
+ });
8
+ };
9
+ export const generateMigrationsVitnode = ({ packageManager: pm, cwd, }) => {
10
+ const packageManager = pm.split('@')[0];
11
+ const args = ['vitnode', 'migrate', '--generate'];
12
+ spawn(packageManager, args, {
13
+ cwd,
14
+ });
15
+ };
@@ -0,0 +1,67 @@
1
+ import { spawn } from 'child_process';
2
+ import color from 'picocolors';
3
+ import { getOnline } from './is-online.js';
4
+ export const installDependencies = async ({ packageManager: pm, cwd, }) => {
5
+ const packageManager = pm.split('@')[0];
6
+ const isOnline = await getOnline();
7
+ const args = ['install'];
8
+ if (!isOnline) {
9
+ console.log(color.yellow('You appear to be offline.\nFalling back to the local cache.'));
10
+ args.push('--offline');
11
+ }
12
+ return new Promise((resolve, reject) => {
13
+ let stdout = '';
14
+ let stderr = '';
15
+ const child = spawn(packageManager, args, {
16
+ stdio: 'pipe',
17
+ cwd,
18
+ env: {
19
+ ...process.env,
20
+ ADBLOCK: '1',
21
+ NODE_ENV: 'development',
22
+ DISABLE_OPENCOLLECTIVE: '1',
23
+ },
24
+ });
25
+ child.stdout?.on('data', (data) => {
26
+ const output = data.toString();
27
+ stdout += output;
28
+ });
29
+ child.stderr?.on('data', (data) => {
30
+ const output = data.toString();
31
+ stderr += output;
32
+ });
33
+ child.on('close', code => {
34
+ if (code !== 0) {
35
+ console.error(color.red(`\n❌ Installation failed with exit code: ${code}`));
36
+ if (stderr) {
37
+ console.error(color.red('Error output:'));
38
+ console.error(stderr);
39
+ }
40
+ if (stdout) {
41
+ console.log(color.yellow('Standard output:'));
42
+ console.log(stdout);
43
+ }
44
+ if (stderr.includes('ENOTFOUND') || stderr.includes('network')) {
45
+ console.error(color.yellow('💡 Network error detected. Please check your internet connection.'));
46
+ }
47
+ else if (stderr.includes('EACCES') ||
48
+ stderr.includes('permission denied')) {
49
+ console.error(color.yellow('💡 Permission error detected. Try running with elevated privileges or check file permissions.'));
50
+ }
51
+ else if (stderr.includes('ENOSPC')) {
52
+ console.error(color.yellow('💡 Disk space error detected. Please free up some disk space.'));
53
+ }
54
+ else if (stderr.includes('ERR_PNPM_PEER_DEP_ISSUES')) {
55
+ console.error(color.yellow('💡 Peer dependency issues detected. Consider using --force flag or resolve conflicts manually.'));
56
+ }
57
+ reject(new Error(`Failed to install dependencies using ${packageManager}. Exit code: ${code}\n${stderr || stdout}`));
58
+ return;
59
+ }
60
+ resolve();
61
+ });
62
+ child.on('error', error => {
63
+ console.error(color.red(`❌ Failed to start ${packageManager}:`), error.message);
64
+ reject(new Error(`Failed to start ${packageManager}: ${error.message}`));
65
+ });
66
+ });
67
+ };
@@ -0,0 +1,49 @@
1
+ import { lstatSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import colors from 'picocolors';
4
+ export function isFolderEmpty(root, name) {
5
+ const validFiles = [
6
+ '.DS_Store',
7
+ '.git',
8
+ '.gitattributes',
9
+ '.gitignore',
10
+ '.gitlab-ci.yml',
11
+ '.hg',
12
+ '.hgcheck',
13
+ '.hgignore',
14
+ '.idea',
15
+ '.npmignore',
16
+ '.travis.yml',
17
+ 'LICENSE',
18
+ 'Thumbs.db',
19
+ 'docs',
20
+ 'mkdocs.yml',
21
+ 'npm-debug.log',
22
+ 'yarn-debug.log',
23
+ 'yarn-error.log',
24
+ 'yarnrc.yml',
25
+ '.yarn',
26
+ ];
27
+ const conflicts = readdirSync(root).filter(file => !validFiles.includes(file) &&
28
+ !file.endsWith('.iml'));
29
+ if (conflicts.length > 0) {
30
+ console.log(`The directory ${colors.green(name)} contains files that could conflict:\n`);
31
+ for (const file of conflicts) {
32
+ try {
33
+ const stats = lstatSync(join(root, file));
34
+ if (stats.isDirectory()) {
35
+ console.log(`${colors.blue(file)}/`);
36
+ }
37
+ else {
38
+ console.log(file);
39
+ }
40
+ }
41
+ catch {
42
+ console.log(file);
43
+ }
44
+ }
45
+ console.log('\nEither try using a new directory name, or remove the files listed above.\n');
46
+ return false;
47
+ }
48
+ return true;
49
+ }
@@ -0,0 +1,38 @@
1
+ import { execSync } from 'child_process';
2
+ import { lookup } from 'dns/promises';
3
+ import { URL } from 'url';
4
+ export async function getOnline() {
5
+ try {
6
+ await lookup('registry.yarnpkg.com');
7
+ return true;
8
+ }
9
+ catch {
10
+ const proxy = getProxy();
11
+ if (!proxy) {
12
+ return false;
13
+ }
14
+ const { hostname } = new URL(proxy);
15
+ if (!hostname) {
16
+ return false;
17
+ }
18
+ try {
19
+ await lookup(hostname);
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ }
27
+ function getProxy() {
28
+ if (process.env.https_proxy) {
29
+ return process.env.https_proxy;
30
+ }
31
+ try {
32
+ const httpsProxy = execSync('npm config get https-proxy').toString().trim();
33
+ return httpsProxy !== 'null' ? httpsProxy : undefined;
34
+ }
35
+ catch {
36
+ return;
37
+ }
38
+ }
@@ -0,0 +1,11 @@
1
+ import { W_OK } from 'constants';
2
+ import { access } from 'fs/promises';
3
+ export async function isWriteable(directory) {
4
+ try {
5
+ await access(directory, W_OK);
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import validateProjectName from 'validate-npm-package-name';
2
+ export const validateNpmName = ({ name, }) => {
3
+ const nameValidation = validateProjectName(name);
4
+ if (nameValidation.validForNewPackages) {
5
+ return { valid: true };
6
+ }
7
+ return {
8
+ valid: false,
9
+ problems: [
10
+ ...(nameValidation.errors ?? []),
11
+ ...(nameValidation.warnings ?? []),
12
+ ],
13
+ };
14
+ };
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ import { input } from '@inquirer/prompts';
3
+ import { Command, Option } from 'commander';
4
+ import { basename, resolve } from 'path';
5
+ import color from 'picocolors';
6
+ import { createVitNode } from './create/create-vitnode.js';
7
+ import { packageJson } from './helpers/get-package-json.js';
8
+ import { validateNpmName } from './helpers/validate-pkg.js';
9
+ import { createPlugin } from './plugin/index.js';
10
+ import { createQuestionsCli } from './questions.js';
11
+ import { validationProject } from './validation.js';
12
+ const [major] = process.versions.node.split('.').map(Number);
13
+ if (major < 20) {
14
+ console.error(color.red(`\nError: VitNode requires Node.js version 20 or higher.\nYou are currently using Node.js ${process.versions.node}\n`));
15
+ process.exit(1);
16
+ }
17
+ process.on('uncaughtException', (error) => {
18
+ if (error.name === 'ExitPromptError') {
19
+ console.log(color.dim('👋 VitNode setup cancelled - see you next time!'));
20
+ process.exit(0);
21
+ }
22
+ console.error(color.red('An unexpected error occurred:'));
23
+ console.error(color.red(error.stack ?? error.message));
24
+ process.exit(1);
25
+ });
26
+ const init = async () => {
27
+ let projectPath = '';
28
+ const program = new Command()
29
+ .version(packageJson.version)
30
+ .argument('[project-directory]')
31
+ .usage(`${color.green('[project-directory]')} [options]`)
32
+ .action(name => {
33
+ projectPath = name;
34
+ });
35
+ program.addOption(new Option('--package-manager <package-manager>', 'Specify the package manager to use').choices(['npm', 'pnpm', 'bun']));
36
+ program.option('--eslint', 'Initialize with eslint config.');
37
+ program.option('--skip-install', 'Skip installing packages after initializing the project.');
38
+ program.option('--plugin', 'Enable plugin mode.');
39
+ program.parse(process.argv);
40
+ const opts = program.opts();
41
+ if (opts.plugin) {
42
+ await createPlugin(projectPath);
43
+ return;
44
+ }
45
+ if (!projectPath) {
46
+ projectPath = await input({
47
+ message: 'What is your project named?',
48
+ default: 'my-vitnode',
49
+ validate: (name) => {
50
+ const validation = validateNpmName({ name: basename(resolve(name)) });
51
+ if (validation.valid)
52
+ return true;
53
+ return `Invalid project name: ${validation.problems[0]}`;
54
+ },
55
+ });
56
+ }
57
+ const { appName, root } = await validationProject(projectPath);
58
+ const options = await createQuestionsCli(program);
59
+ await createVitNode({
60
+ appName,
61
+ root,
62
+ ...options,
63
+ });
64
+ };
65
+ await init();
@@ -0,0 +1,18 @@
1
+ import { input } from '@inquirer/prompts';
2
+ import { basename, resolve } from 'path';
3
+ import { validateNpmName } from '../helpers/validate-pkg.js';
4
+ export const createPlugin = async (projectPath) => {
5
+ let name = projectPath;
6
+ if (!name) {
7
+ name = await input({
8
+ message: 'What is your plugin named?',
9
+ default: 'my-vitnode-plugin',
10
+ validate: (name) => {
11
+ const validation = validateNpmName({ name: basename(resolve(name)) });
12
+ if (validation.valid)
13
+ return true;
14
+ return `Invalid plugin name: ${validation.problems[0]}`;
15
+ },
16
+ });
17
+ }
18
+ };
@@ -0,0 +1,16 @@
1
+ import { existsSync } from 'fs';
2
+ import { mkdir } from 'fs/promises';
3
+ import { join } from 'path';
4
+ const prepare = async () => {
5
+ const toRootPath = join(process.cwd(), 'copy-of-vitnode-app');
6
+ if (!existsSync(toRootPath)) {
7
+ await mkdir(toRootPath);
8
+ }
9
+ const fromRootPath = join(process.cwd(), '..', '..', 'apps', 'docs');
10
+ if (!existsSync(fromRootPath)) {
11
+ console.error(`\x1b[31mThe path ${fromRootPath} does not exist. Please check the directory structure.\x1b[0m`);
12
+ process.exit(1);
13
+ }
14
+ console.log(`Project path: ${fromRootPath}`);
15
+ };
16
+ void prepare();
@@ -0,0 +1,52 @@
1
+ import { select } from '@inquirer/prompts';
2
+ import { confirm } from '@inquirer/prompts';
3
+ import color from 'picocolors';
4
+ import { getAvailablePackageManagers } from './helpers/get-available-package-managers.js';
5
+ export const createQuestionsCli = async (program) => {
6
+ const optionsFromProgram = program.opts();
7
+ const options = {
8
+ packageManager: optionsFromProgram.packageManager,
9
+ eslint: optionsFromProgram.eslint,
10
+ install: !optionsFromProgram.skipInstall,
11
+ docker: optionsFromProgram.docker,
12
+ };
13
+ if (!optionsFromProgram.packageManager) {
14
+ const availablePackageManagers = await getAvailablePackageManagers();
15
+ options.packageManager = await select({
16
+ message: `Which ${color.blue('package manager')} do you want to use?`,
17
+ choices: [
18
+ {
19
+ name: `npm${availablePackageManagers.npm ? `@${availablePackageManagers.npm}` : ''}`,
20
+ value: 'npm',
21
+ disabled: !availablePackageManagers.npm,
22
+ },
23
+ {
24
+ name: `pnpm${availablePackageManagers.pnpm ? `@${availablePackageManagers.pnpm}` : ''}`,
25
+ value: 'pnpm',
26
+ disabled: !availablePackageManagers.pnpm,
27
+ },
28
+ {
29
+ name: `bun${availablePackageManagers.bun ? `@${availablePackageManagers.bun}` : ''}`,
30
+ value: 'bun',
31
+ disabled: !availablePackageManagers.bun,
32
+ },
33
+ ],
34
+ });
35
+ }
36
+ if (optionsFromProgram.eslint === undefined) {
37
+ options.eslint = await confirm({
38
+ message: `Would you like to use ${color.blue('ESLint')}?`,
39
+ });
40
+ }
41
+ if (optionsFromProgram.docker === undefined) {
42
+ options.docker = await confirm({
43
+ message: `Would you like to use ${color.blue('Docker Container')}?`,
44
+ });
45
+ }
46
+ if (optionsFromProgram.skipInstall === undefined) {
47
+ options.install = await confirm({
48
+ message: `Would you like to ${color.blue('Install dependencies')}?`,
49
+ });
50
+ }
51
+ return options;
52
+ };
@@ -0,0 +1,40 @@
1
+ import { program } from 'commander';
2
+ import { existsSync } from 'fs';
3
+ import { basename, dirname, resolve } from 'path';
4
+ import color from 'picocolors';
5
+ import { isFolderEmpty } from './helpers/is-folder-empty.js';
6
+ import { isWriteable } from './helpers/is-writeable.js';
7
+ import { validateNpmName } from './helpers/validate-pkg.js';
8
+ export const validationProject = async (projectPath) => {
9
+ if (!projectPath) {
10
+ console.log('\nPlease specify the project directory:\n' +
11
+ ` ${color.cyan(program.name())} ${color.green('<project-directory>')}\n` +
12
+ 'For example:\n' +
13
+ ` ${color.cyan(program.name())} ${color.green('my-vitnode-app')}\n\n` +
14
+ `Run ${color.cyan(`${program.name()} --help`)} to see all options.`);
15
+ process.exit(1);
16
+ }
17
+ const resolvedProjectPath = resolve(projectPath);
18
+ const projectName = basename(resolvedProjectPath);
19
+ const validation = validateNpmName({ name: projectName });
20
+ if (!validation.valid) {
21
+ console.error(`Could not create a project called ${color.red(`"${projectName}"`)} because of npm naming restrictions:`);
22
+ validation.problems.forEach(p => {
23
+ console.error(`${color.red(color.bold('*'))} ${p}`);
24
+ });
25
+ process.exit(1);
26
+ }
27
+ const root = resolve(resolvedProjectPath);
28
+ const appName = basename(root);
29
+ const folderExists = existsSync(root);
30
+ if (folderExists && !isFolderEmpty(root, appName)) {
31
+ console.error('The specified directory is not empty.');
32
+ process.exit(1);
33
+ }
34
+ if (!(await isWriteable(dirname(root)))) {
35
+ console.error('The application path is not writable, please check folder permissions and try again.');
36
+ console.error('It is likely you do not have write permissions for this folder.');
37
+ process.exit(1);
38
+ }
39
+ return { appName, root };
40
+ };