create-vxrn 0.1.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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/biome.json +78 -0
  3. package/dist/helpers/cloneStarter.js +79 -0
  4. package/dist/helpers/cloneStarter.js.map +6 -0
  5. package/dist/helpers/cloneStarter.native.js +320 -0
  6. package/dist/helpers/cloneStarter.native.js.map +6 -0
  7. package/dist/helpers/getProjectName.js +45 -0
  8. package/dist/helpers/getProjectName.js.map +6 -0
  9. package/dist/helpers/getProjectName.native.js +185 -0
  10. package/dist/helpers/getProjectName.native.js.map +6 -0
  11. package/dist/helpers/getTemplateInfo.js +38 -0
  12. package/dist/helpers/getTemplateInfo.js.map +6 -0
  13. package/dist/helpers/getTemplateInfo.native.js +191 -0
  14. package/dist/helpers/getTemplateInfo.native.js.map +6 -0
  15. package/dist/helpers/installDependencies.js +33 -0
  16. package/dist/helpers/installDependencies.js.map +6 -0
  17. package/dist/helpers/installDependencies.native.js +187 -0
  18. package/dist/helpers/installDependencies.native.js.map +6 -0
  19. package/dist/helpers/validateNpmPackage.js +36 -0
  20. package/dist/helpers/validateNpmPackage.js.map +6 -0
  21. package/dist/helpers/validateNpmPackage.native.js +74 -0
  22. package/dist/helpers/validateNpmPackage.native.js.map +6 -0
  23. package/dist/index.js +114 -0
  24. package/dist/index.js.map +6 -0
  25. package/dist/index.native.js +300 -0
  26. package/dist/index.native.js.map +6 -0
  27. package/dist/steps/bare.js +38 -0
  28. package/dist/steps/bare.js.map +6 -0
  29. package/dist/steps/bare.native.js +163 -0
  30. package/dist/steps/bare.native.js.map +6 -0
  31. package/dist/steps/expo-router.js +41 -0
  32. package/dist/steps/expo-router.js.map +6 -0
  33. package/dist/steps/expo-router.native.js +168 -0
  34. package/dist/steps/expo-router.native.js.map +6 -0
  35. package/dist/steps/tamagui.js +41 -0
  36. package/dist/steps/tamagui.js.map +6 -0
  37. package/dist/steps/tamagui.native.js +168 -0
  38. package/dist/steps/tamagui.native.js.map +6 -0
  39. package/dist/steps/types.js +14 -0
  40. package/dist/steps/types.js.map +6 -0
  41. package/dist/steps/types.native.js +15 -0
  42. package/dist/steps/types.native.js.map +6 -0
  43. package/dist/templates.js +84 -0
  44. package/dist/templates.js.map +6 -0
  45. package/dist/templates.native.js +97 -0
  46. package/dist/templates.native.js.map +6 -0
  47. package/package.json +33 -0
  48. package/readme.md +5 -0
  49. package/run.js +12 -0
  50. package/src/helpers/cloneStarter.ts +115 -0
  51. package/src/helpers/getProjectName.ts +46 -0
  52. package/src/helpers/getTemplateInfo.ts +32 -0
  53. package/src/helpers/installDependencies.ts +14 -0
  54. package/src/helpers/validateNpmPackage.ts +16 -0
  55. package/src/index.ts +170 -0
  56. package/src/steps/bare.ts +21 -0
  57. package/src/steps/expo-router.ts +25 -0
  58. package/src/steps/tamagui.ts +25 -0
  59. package/src/steps/types.ts +5 -0
  60. package/src/templates.ts +62 -0
  61. package/tsconfig.json +12 -0
@@ -0,0 +1,84 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: !0 });
9
+ }, __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from == "object" || typeof from == "function")
11
+ for (let key of __getOwnPropNames(from))
12
+ !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ return to;
14
+ };
15
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
16
+ // If the importer is in node compatibility mode or this is not an ESM
17
+ // file that has been converted to a CommonJS file using a Babel-
18
+ // compatible transform (i.e. "__esModule" has not been set), then set
19
+ // "default" to the CommonJS "module.exports" for node compatibility.
20
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target,
21
+ mod
22
+ )), __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod);
23
+ var templates_exports = {};
24
+ __export(templates_exports, {
25
+ templates: () => templates
26
+ });
27
+ module.exports = __toCommonJS(templates_exports);
28
+ var import_bare = __toESM(require("./steps/bare")), import_expo_router = __toESM(require("./steps/expo-router")), import_tamagui = __toESM(require("./steps/tamagui"));
29
+ const templates = [
30
+ // {
31
+ // title: `Free - Expo + Next in a production ready monorepo`,
32
+ // value: 'starter-free',
33
+ // type: 'free',
34
+ // hidden: false,
35
+ // packageManager: 'yarn',
36
+ // repo: {
37
+ // url: `https://github.com/tamagui/starter-free.git`,
38
+ // sshFallback: `git@github.com:tamagui/starter-free.git`,
39
+ // dir: [],
40
+ // branch: 'main',
41
+ // },
42
+ // extraSteps: starterFree,
43
+ // },
44
+ {
45
+ title: "Bare",
46
+ value: "bare",
47
+ type: "included-in-monorepo",
48
+ hidden: !1,
49
+ repo: {
50
+ url: "https://github.com/natew/vxrn.git",
51
+ sshFallback: "git@github.com:natew/vxrn.git",
52
+ dir: ["examples", "vxrn"],
53
+ branch: "main"
54
+ },
55
+ extraSteps: import_bare.default
56
+ },
57
+ {
58
+ title: "Tamagui",
59
+ value: "tamagui",
60
+ type: "included-in-monorepo",
61
+ hidden: !1,
62
+ repo: {
63
+ url: "https://github.com/natew/vxrn.git",
64
+ sshFallback: "git@github.com:natew/vxrn.git",
65
+ dir: ["examples", "tamagui"],
66
+ branch: "main"
67
+ },
68
+ extraSteps: import_tamagui.default
69
+ },
70
+ {
71
+ title: "Expo Router",
72
+ value: "expo-router",
73
+ type: "included-in-monorepo",
74
+ hidden: !1,
75
+ repo: {
76
+ url: "https://github.com/natew/vxrn.git",
77
+ sshFallback: "git@github.com:natew/vxrn.git",
78
+ dir: ["examples", "expo-router"],
79
+ branch: "main"
80
+ },
81
+ extraSteps: import_expo_router.default
82
+ }
83
+ ];
84
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/templates.ts"],
4
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAsB,kCACtB,qBAA4B,yCAC5B,iBAAyB;AAElB,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBvB;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,MACJ,KAAK;AAAA,MACL,aAAa;AAAA,MACb,KAAK,CAAC,YAAY,MAAM;AAAA,MACxB,QAAQ;AAAA,IACV;AAAA,IACA,YAAY,YAAAA;AAAA,EACd;AAAA,EAEA;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,MACJ,KAAK;AAAA,MACL,aAAa;AAAA,MACb,KAAK,CAAC,YAAY,SAAS;AAAA,MAC3B,QAAQ;AAAA,IACV;AAAA,IACA,YAAY,eAAAC;AAAA,EACd;AAAA,EAEA;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,MACJ,KAAK;AAAA,MACL,aAAa;AAAA,MACb,KAAK,CAAC,YAAY,aAAa;AAAA,MAC/B,QAAQ;AAAA,IACV;AAAA,IACA,YAAY,mBAAAC;AAAA,EACd;AACF;",
5
+ "names": ["stepsBare", "stepsTamagui", "stepsExpoRouter"]
6
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: !0 });
10
+ }, __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from == "object" || typeof from == "function")
12
+ for (let key of __getOwnPropNames(from))
13
+ !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target,
22
+ mod
23
+ )), __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod);
24
+ var templates_exports = {};
25
+ __export(templates_exports, {
26
+ templates: () => templates
27
+ });
28
+ module.exports = __toCommonJS(templates_exports);
29
+ var import_bare = __toESM(require("./steps/bare")), import_expo_router = __toESM(require("./steps/expo-router")), import_tamagui = __toESM(require("./steps/tamagui")), templates = [
30
+ // {
31
+ // title: `Free - Expo + Next in a production ready monorepo`,
32
+ // value: 'starter-free',
33
+ // type: 'free',
34
+ // hidden: false,
35
+ // packageManager: 'yarn',
36
+ // repo: {
37
+ // url: `https://github.com/tamagui/starter-free.git`,
38
+ // sshFallback: `git@github.com:tamagui/starter-free.git`,
39
+ // dir: [],
40
+ // branch: 'main',
41
+ // },
42
+ // extraSteps: starterFree,
43
+ // },
44
+ {
45
+ title: "Bare",
46
+ value: "bare",
47
+ type: "included-in-monorepo",
48
+ hidden: !1,
49
+ repo: {
50
+ url: "https://github.com/natew/vxrn.git",
51
+ sshFallback: "git@github.com:natew/vxrn.git",
52
+ dir: [
53
+ "examples",
54
+ "vxrn"
55
+ ],
56
+ branch: "main"
57
+ },
58
+ extraSteps: import_bare.default
59
+ },
60
+ {
61
+ title: "Tamagui",
62
+ value: "tamagui",
63
+ type: "included-in-monorepo",
64
+ hidden: !1,
65
+ repo: {
66
+ url: "https://github.com/natew/vxrn.git",
67
+ sshFallback: "git@github.com:natew/vxrn.git",
68
+ dir: [
69
+ "examples",
70
+ "tamagui"
71
+ ],
72
+ branch: "main"
73
+ },
74
+ extraSteps: import_tamagui.default
75
+ },
76
+ {
77
+ title: "Expo Router",
78
+ value: "expo-router",
79
+ type: "included-in-monorepo",
80
+ hidden: !1,
81
+ repo: {
82
+ url: "https://github.com/natew/vxrn.git",
83
+ sshFallback: "git@github.com:natew/vxrn.git",
84
+ dir: [
85
+ "examples",
86
+ "expo-router"
87
+ ],
88
+ branch: "main"
89
+ },
90
+ extraSteps: import_expo_router.default
91
+ }
92
+ ];
93
+ // Annotate the CommonJS export names for ESM import in node:
94
+ 0 && (module.exports = {
95
+ templates
96
+ });
97
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/Users/n8/vxrn/packages/create-vxrn/src/templates.ts"],
4
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;kBAAsB,kCACtB,qBAA4B,yCAC5B,iBAAyB,qCAEZA,YAAY;;;;;;;;;;;;;;;EAgBvB;IACEC,OAAQ;IACRC,OAAO;IACPC,MAAM;IACNC,QAAQ;IACRC,MAAM;MACJC,KAAM;MACNC,aAAc;MACdC,KAAK;QAAE;QAAY;;MACnBC,QAAQ;IACV;IACAC,YAAYC,YAAAA;EACd;EAEA;IACEV,OAAQ;IACRC,OAAO;IACPC,MAAM;IACNC,QAAQ;IACRC,MAAM;MACJC,KAAM;MACNC,aAAc;MACdC,KAAK;QAAE;QAAY;;MACnBC,QAAQ;IACV;IACAC,YAAYE,eAAAA;EACd;EAEA;IACEX,OAAQ;IACRC,OAAO;IACPC,MAAM;IACNC,QAAQ;IACRC,MAAM;MACJC,KAAM;MACNC,aAAc;MACdC,KAAK;QAAE;QAAY;;MACnBC,QAAQ;IACV;IACAC,YAAYG,mBAAAA;EACd;;",
5
+ "names": ["templates", "title", "value", "type", "hidden", "repo", "url", "sshFallback", "dir", "branch", "extraSteps", "stepsBare", "stepsTamagui", "stepsExpoRouter"]
6
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "create-vxrn",
3
+ "version": "0.1.0",
4
+ "bin": "./run.js",
5
+ "main": "dist",
6
+ "scripts": {
7
+ "start": "./run.js",
8
+ "prerelease": "rimraf ./dist/",
9
+ "prepublish": "yarn build",
10
+ "build": "tamagui-build --skip-types",
11
+ "watch": "yarn build --watch",
12
+ "clean": "tamagui-build clean",
13
+ "clean:build": "tamagui-build clean:build"
14
+ },
15
+ "dependencies": {
16
+ "@expo/package-manager": "^1.1.2",
17
+ "@tamagui/build": "^1.94.4",
18
+ "@types/prompts": "^2.4.9",
19
+ "@types/validate-npm-package-name": "^4.0.2",
20
+ "ansis": "^3.1.0",
21
+ "async-retry": "1.3.1",
22
+ "citty": "^0.1.6",
23
+ "cpy": "^11.0.1",
24
+ "detect-package-manager": "^3.0.1",
25
+ "fs-extra": "^11.2.0",
26
+ "prompts": "2.1.0",
27
+ "rimraf": "^5.0.1",
28
+ "validate-npm-package-name": "3.0.0"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }
package/readme.md ADDED
@@ -0,0 +1,5 @@
1
+ # `create-vxrn`
2
+
3
+ ```sh
4
+ npm create vxrn
5
+ ```
package/run.js ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ const command = require.resolve('create-vxrn')
4
+ const args = process.argv.slice(2)
5
+
6
+ try {
7
+ require('node:child_process').execSync(`node ${command} ${args.join(' ')}`, {
8
+ stdio: 'inherit',
9
+ })
10
+ } catch (err) {
11
+ process.exit(1)
12
+ }
@@ -0,0 +1,115 @@
1
+ import { execSync } from 'node:child_process'
2
+ import { homedir } from 'node:os'
3
+ import { join } from 'node:path'
4
+
5
+ import ansis from 'ansis'
6
+ import { copy, ensureDir, pathExists, remove } from 'fs-extra'
7
+ import { rimraf } from 'rimraf'
8
+
9
+ import type { templates } from '../templates'
10
+
11
+ const exec = (cmd: string, options?: Parameters<typeof execSync>[1]) => {
12
+ return execSync(cmd, {
13
+ stdio: process.env.DEBUG ? 'inherit' : 'ignore',
14
+ ...options,
15
+ })
16
+ }
17
+
18
+ const home = homedir()
19
+ const vxrnDir = join(home, '.vxrn')
20
+ let targetGitDir = ''
21
+
22
+ export const cloneStarter = async (
23
+ template: (typeof templates)[number],
24
+ resolvedProjectPath: string,
25
+ projectName: string
26
+ ) => {
27
+ targetGitDir = join(vxrnDir, 'vxrn', template.repo.url.split('/').at(-1)!)
28
+
29
+ console.info()
30
+ await setupVxrnDotDir(template)
31
+ const starterDir = join(targetGitDir, ...template.repo.dir)
32
+ console.info()
33
+ console.info(`Copying starter from ${starterDir} into ${ansis.blueBright(projectName)}...`)
34
+ console.info()
35
+
36
+ // if (!(await pathExists(starterDir))) {
37
+ // console.error(`Missing template for ${template.value} in ${starterDir}`)
38
+ // process.exit(1)
39
+ // }
40
+ await copy(starterDir, resolvedProjectPath)
41
+ await rimraf(`${resolvedProjectPath}/.git`)
42
+
43
+ console.info(ansis.green(`${projectName} created!`))
44
+ console.info()
45
+ }
46
+
47
+ async function setupVxrnDotDir(template: (typeof templates)[number], isRetry = false) {
48
+ const repoRoot = join(__dirname, '..', '..', '..')
49
+
50
+ console.info(`Setting up ${ansis.blueBright(targetGitDir)}...`)
51
+
52
+ const branch = template.repo.branch
53
+
54
+ await ensureDir(vxrnDir)
55
+
56
+ const isInSubDir = template.repo.dir.length > 0
57
+
58
+ if (!(await pathExists(targetGitDir))) {
59
+ console.info(`Cloning vxrn base directory`)
60
+ console.info()
61
+
62
+ const sourceGitRepo = template.repo.url
63
+ const sourceGitRepoSshFallback = template.repo.sshFallback
64
+
65
+ const cmd = `git clone --branch ${branch} ${
66
+ isInSubDir ? '--depth 1 --sparse --filter=blob:none ' : ''
67
+ }${sourceGitRepo} "${targetGitDir}"`
68
+
69
+ try {
70
+ console.info(`$ ${cmd}`)
71
+ console.info()
72
+ exec(cmd)
73
+ } catch (error) {
74
+ if (cmd.includes('https://')) {
75
+ console.info(`https failed - trying with ssh now...`)
76
+ const sshCmd = cmd.replace(sourceGitRepo, sourceGitRepoSshFallback)
77
+ console.info(`$ ${sshCmd}`)
78
+ console.info()
79
+ exec(sshCmd)
80
+ } else {
81
+ throw error
82
+ }
83
+ }
84
+ } else {
85
+ if (!(await pathExists(join(targetGitDir, '.git')))) {
86
+ console.error(`Corrupt Vxrn directory, please delete ${targetGitDir} and re-run`)
87
+ process.exit(1)
88
+ }
89
+ }
90
+
91
+ if (isInSubDir) {
92
+ const cmd = `git sparse-checkout set ${template.repo.dir[0] ?? '.'}`
93
+ exec(cmd, { cwd: targetGitDir })
94
+ console.info()
95
+ }
96
+ try {
97
+ const cmd2 = `git pull --rebase --allow-unrelated-histories --depth 1 origin ${branch}`
98
+ exec(cmd2, {
99
+ cwd: targetGitDir,
100
+ })
101
+ console.info()
102
+ } catch (err: any) {
103
+ console.info(
104
+ `Error updating: ${err.message} ${isRetry ? `failing.\n${err.stack}` : 'trying from fresh.'}`
105
+ )
106
+ if (isRetry) {
107
+ console.info(
108
+ `Please file an issue: https://github.com/vxrn/vxrn/issues/new?assignees=&labels=&template=bug_report.md&title=`
109
+ )
110
+ process.exit(1)
111
+ }
112
+ await remove(targetGitDir)
113
+ await setupVxrnDotDir(template, true)
114
+ }
115
+ }
@@ -0,0 +1,46 @@
1
+ import path from 'node:path'
2
+
3
+ import ansis from 'ansis'
4
+ import prompts from 'prompts'
5
+
6
+ import packageJson from '../../package.json'
7
+ import { validateNpmName } from './validateNpmPackage'
8
+
9
+ export const getProjectName = async (projectPath?: string) => {
10
+ if (typeof projectPath === 'string') {
11
+ projectPath = projectPath.trim()
12
+ }
13
+
14
+ if (!projectPath) {
15
+ const res = await prompts({
16
+ type: 'text',
17
+ name: 'path',
18
+ message: 'Project name:',
19
+ initial: 'myapp',
20
+ validate: (name) => {
21
+ const validation = validateNpmName(path.basename(path.resolve(name)))
22
+ if (validation.valid) {
23
+ return true
24
+ }
25
+ return 'Invalid project name: ' + validation.problems![0]
26
+ },
27
+ })
28
+
29
+ if (typeof res.path === 'string') {
30
+ projectPath = res.path.trim()
31
+ }
32
+ }
33
+
34
+ if (!projectPath) {
35
+ console.info()
36
+ console.info('Please specify the project directory:')
37
+ console.info(` ${ansis.cyan(packageJson.name)} ${ansis.green('<project-directory>')}`)
38
+ console.info()
39
+ console.info('For example:')
40
+ console.info(` ${ansis.cyan(packageJson.name)} ${ansis.green('my-app')}`)
41
+ console.info()
42
+ console.info(`Run ${ansis.cyan(`${packageJson.name} --help`)} to see all options.`)
43
+ process.exit(1)
44
+ }
45
+ return projectPath
46
+ }
@@ -0,0 +1,32 @@
1
+ import prompts from 'prompts'
2
+
3
+ import { templates } from '../templates'
4
+
5
+ const validTemplates = templates.map(({ value }) => value).join(', ')
6
+
7
+ export const getTemplateInfo = async (template?: string): Promise<(typeof templates)[number]> => {
8
+ let res = getValidTemplate(template)
9
+ if (template && !res) {
10
+ console.warn(`template ${template} is not valid. valid options: ${validTemplates}`)
11
+ process.exit(1)
12
+ }
13
+ if (!res) {
14
+ template = (
15
+ await prompts({
16
+ name: 'template',
17
+ type: 'select',
18
+ message: `Pick a template:`,
19
+ choices: templates.filter((t) => !t.hidden),
20
+ })
21
+ ).template
22
+ }
23
+ res = getValidTemplate(`${template}`)
24
+ if (!res) {
25
+ console.warn(`template ${template} is not valid. valid options: ${validTemplates}`)
26
+ process.exit(1)
27
+ }
28
+ return res
29
+ }
30
+
31
+ const getValidTemplate = (template?: string) =>
32
+ typeof template === 'string' && templates.find(({ value }) => value === template)
@@ -0,0 +1,14 @@
1
+ import * as PackageManager from '@expo/package-manager'
2
+
3
+ export async function installDependencies(
4
+ projectRoot: string,
5
+ packageManager: 'yarn' | 'npm' | 'pnpm' | 'bun'
6
+ ) {
7
+ const options = { cwd: projectRoot }
8
+ if (packageManager === 'yarn') {
9
+ const yarn = new PackageManager.YarnPackageManager(options)
10
+ await yarn.installAsync()
11
+ } else {
12
+ await new PackageManager.NpmPackageManager(options).installAsync()
13
+ }
14
+ }
@@ -0,0 +1,16 @@
1
+ import validateProjectName from 'validate-npm-package-name'
2
+
3
+ export function validateNpmName(name: string): {
4
+ valid: boolean
5
+ problems?: string[]
6
+ } {
7
+ const nameValidation = validateProjectName(name)
8
+ if (nameValidation.validForNewPackages) {
9
+ return { valid: true }
10
+ }
11
+
12
+ return {
13
+ valid: false,
14
+ problems: [...(nameValidation.errors || []), ...(nameValidation.warnings || [])],
15
+ }
16
+ }
package/src/index.ts ADDED
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+ // inspired by https://github.com/vercel/next.js/blob/0355e5f63f87db489f36db8d814958cb4c2b828b/packages/create-next-app/helpers/examples.ts#L71
3
+
4
+ import ansis from 'ansis'
5
+ import { defineCommand, runMain } from 'citty'
6
+ import { detect } from 'detect-package-manager'
7
+ import { existsSync, readFileSync, writeFileSync } from 'fs-extra'
8
+ import { execSync } from 'node:child_process'
9
+ import fs from 'node:fs'
10
+ import path from 'node:path'
11
+ import { cwd } from 'node:process'
12
+ import packageJson from '../package.json'
13
+ import { cloneStarter } from './helpers/cloneStarter'
14
+ import { getProjectName } from './helpers/getProjectName'
15
+ import { getTemplateInfo } from './helpers/getTemplateInfo'
16
+ import { installDependencies } from './helpers/installDependencies'
17
+ import { validateNpmName } from './helpers/validateNpmPackage'
18
+
19
+ let projectPath = ''
20
+
21
+ function exit() {
22
+ process.exit(0)
23
+ }
24
+
25
+ process.on('SIGTERM', exit)
26
+ process.on('SIGINT', exit)
27
+
28
+ const main = defineCommand({
29
+ meta: {
30
+ name: 'main',
31
+ version: '0.0.0',
32
+ description: 'Welcome to vxrn',
33
+ },
34
+ args: {
35
+ directory: {
36
+ type: 'positional',
37
+ description: 'Directory to copy into',
38
+ default: '',
39
+ },
40
+ template: {
41
+ type: 'string',
42
+ default: 'bare',
43
+ description: 'One of "bare", "tamagui", "expo-router".',
44
+ },
45
+ info: {
46
+ type: 'boolean',
47
+ description: 'Output the post-install instructions for the template.',
48
+ },
49
+ },
50
+ async run({ args }) {
51
+ if (args.info) {
52
+ let template = await getTemplateInfo(args.template)
53
+ await template.extraSteps({
54
+ isFullClone: false,
55
+ projectName: path.basename(cwd()),
56
+ projectPath: cwd(),
57
+ })
58
+ return
59
+ }
60
+
61
+ console.info()
62
+ console.info(
63
+ ansis.bold(' Note: You may need to run "npm create vxrn@latest" to get the latest version!')
64
+ )
65
+ console.info()
66
+
67
+ console.info() // this newline prevents the ascii art from breaking
68
+ console.info(ansis.bold('Creating vxrn app...'))
69
+
70
+ const gitVersionString = Number.parseFloat(
71
+ execSync(`git --version`).toString().replace(`git version `, '').trim()
72
+ )
73
+ if (gitVersionString < 2.27) {
74
+ console.error(`\n\n ⚠️ vxrn can't install: Git version must be >= 2.27\n\n`)
75
+ process.exit(1)
76
+ }
77
+
78
+ projectPath ||= await getProjectName(projectPath)
79
+
80
+ let template = await getTemplateInfo(args.template)
81
+
82
+ // space
83
+ console.info()
84
+
85
+ const resolvedProjectPath = path.resolve(process.cwd(), projectPath)
86
+ const projectName = path.basename(resolvedProjectPath)
87
+
88
+ const { valid, problems } = validateNpmName(projectName)
89
+ if (!valid) {
90
+ console.error(
91
+ `Could not create a project called ${ansis.red(
92
+ `"${projectName}"`
93
+ )} because of npm naming restrictions:`
94
+ )
95
+
96
+ problems!.forEach((p) => console.error(` ${ansis.red.bold('*')} ${p}`))
97
+ process.exit(1)
98
+ }
99
+
100
+ if (fs.existsSync(resolvedProjectPath)) {
101
+ console.info()
102
+ console.info(
103
+ ansis.red('🚨 [vxrn] error'),
104
+ `You tried to make a project called ${ansis.underline(
105
+ ansis.blueBright(projectName)
106
+ )}, but a folder with that name already exists: ${ansis.blueBright(resolvedProjectPath)}
107
+
108
+ ${ansis.bold(ansis.red(`Please pick a different project name 🥸`))}`
109
+ )
110
+ console.info()
111
+ console.info()
112
+ process.exit(1)
113
+ }
114
+ console.info()
115
+ console.info(`Creating a new vxrn app ${ansis.blueBright(resolvedProjectPath)}...`)
116
+ fs.mkdirSync(resolvedProjectPath)
117
+ console.info(ansis.green(`${projectName} folder created.`))
118
+
119
+ try {
120
+ await cloneStarter(template, resolvedProjectPath, projectName)
121
+ process.chdir(resolvedProjectPath)
122
+ // space
123
+ console.info()
124
+ } catch (e) {
125
+ console.error(`[vxrn] Failed to copy example into ${resolvedProjectPath}\n\n`, e)
126
+ process.exit(1)
127
+ }
128
+
129
+ // change root package.json's name to project name
130
+ updatePackageJsonName(projectName, resolvedProjectPath)
131
+
132
+ console.info('Installing packages. This might take a couple of minutes.')
133
+ console.info()
134
+
135
+ const packageManager =
136
+ ('packageManager' in template ? template.packageManager : undefined) || (await detect())
137
+
138
+ try {
139
+ console.info('installing with ' + packageManager)
140
+ await installDependencies(resolvedProjectPath, packageManager as any)
141
+ } catch (e: any) {
142
+ console.error('[vxrn] error installing with ' + packageManager + '\n' + `${e}`)
143
+ process.exit(1)
144
+ }
145
+
146
+ await template.extraSteps({
147
+ isFullClone: true,
148
+ projectName,
149
+ projectPath: resolvedProjectPath,
150
+ })
151
+
152
+ console.info()
153
+ },
154
+ })
155
+
156
+ runMain(main)
157
+
158
+ if (process.argv.includes('--version')) {
159
+ console.info(packageJson.version)
160
+ process.exit(0)
161
+ }
162
+
163
+ function updatePackageJsonName(projectName: string, dir: string) {
164
+ const packageJsonPath = path.join(dir, 'package.json')
165
+ if (existsSync(packageJsonPath)) {
166
+ const content = readFileSync(packageJsonPath).toString()
167
+ const contentWithUpdatedName = content.replace(/("name": ")(.*)(",)/, `$1${projectName}$3`)
168
+ writeFileSync(packageJsonPath, contentWithUpdatedName)
169
+ }
170
+ }
@@ -0,0 +1,21 @@
1
+ import ansis from 'ansis'
2
+
3
+ import type { ExtraSteps } from './types'
4
+
5
+ const packageManager = 'yarn'
6
+ const useYarn = packageManager === 'yarn'
7
+
8
+ const runCommand = (scriptName: string) => `${packageManager} ${useYarn ? '' : 'run '}${scriptName}`
9
+
10
+ const main: ExtraSteps = async ({ isFullClone, projectName }) => {
11
+ if (isFullClone) {
12
+ console.info(`
13
+ ${ansis.green.bold('Done!')} created a new project under ./${projectName}
14
+
15
+ visit your project:
16
+ ${ansis.green('cd')} ${projectName}
17
+ `)
18
+ }
19
+ }
20
+
21
+ export default main