create-insystem-native-app 0.0.1

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 (3) hide show
  1. package/README.md +86 -0
  2. package/index.js +246 -0
  3. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # create-insystem-native-app
2
+
3
+ INSYSTEM 하이브리드 앱 보일러플레이트(Expo + WebView)를 클론해 새 프로젝트를 만듭니다.
4
+
5
+ 웹 서비스를 그대로 두고 Android·iOS 앱으로 감싸는 네이티브 셸입니다.
6
+ 푸시 알림, 딥링크, 외부 결제창, 웹↔네이티브 브리지가 들어 있습니다.
7
+
8
+ `create-insystem-nest-app` 과 같은 방식입니다.
9
+
10
+ ## 사용법
11
+
12
+ ```bash
13
+ npx create-insystem-native-app ./my-app
14
+ ```
15
+
16
+ ```bash
17
+ create-insystem-native-app <target-dir> [options]
18
+ ```
19
+
20
+ | 옵션 | 설명 |
21
+ | --- | --- |
22
+ | `--branch <branch>` | 클론할 브랜치 (기본 `template`) |
23
+ | `--repo <url>` | 템플릿 git 주소 |
24
+ | `--no-install` | 의존성 설치 건너뛰기 |
25
+ | `--no-env` | `.env.example` → `.env` 복사 건너뛰기 |
26
+ | `--git` | 클론 후 새 git 저장소로 초기화 |
27
+ | `-h`, `--help` | 도움말 |
28
+
29
+ ## 하는 일
30
+
31
+ 1. `insystem-dev01/insystem-shop-web-app` 의 **`template` 브랜치**를 `--depth 1` 로 클론
32
+ 2. `.git` 제거 (새 프로젝트는 자기 히스토리로 시작)
33
+ 3. `.env.example` → `.env` 복사
34
+ 4. 의존성 설치 (npm/yarn/pnpm/bun 자동 감지)
35
+ 5. 다음 단계 안내 출력
36
+
37
+ > ⚠️ **기본 브랜치가 `main` 이 아니라 `template` 입니다.**
38
+ > 이 저장소의 `main` 은 인시스템 마트 고객앱의 실제 배포본이라
39
+ > 브랜드·패키지명·Firebase 설정이 들어 있습니다. 새 고객사 시작점으로는 맞지 않습니다.
40
+ > 마트 앱 자체를 복제해야 한다면 `--branch main` 을 쓰세요.
41
+
42
+ ## 클론 후 반드시 할 일
43
+
44
+ **① `.env` 수정** — 이 값이 없으면 앱이 띄울 웹 주소를 몰라 실행되지 않습니다.
45
+
46
+ ```bash
47
+ EXPO_PUBLIC_WEB_APP_URL=https://shop.example.com # 필수
48
+ APP_DISPLAY_NAME=고객사 앱 이름
49
+ APP_SCHEME=customerapp # 딥링크 스킴
50
+ ANDROID_PACKAGE=kr.example.customer
51
+ IOS_BUNDLE_IDENTIFIER=kr.example.customer
52
+ ```
53
+
54
+ > 실기기에서 개발 서버를 볼 때 `localhost` 는 **휴대폰 자신**을 가리킵니다.
55
+ > 개발 PC 의 LAN IP 를 넣으세요. (안드로이드 에뮬레이터는 `http://10.0.2.2:3000`)
56
+
57
+ **② 푸시를 쓴다면 Firebase 파일 배치**
58
+
59
+ ```
60
+ config/firebase/google-services.json (Android)
61
+ config/firebase/GoogleService-Info.plist (iOS)
62
+ ```
63
+
64
+ 🚨 이 파일들은 **고객사마다 다르고 저장소에 커밋되지 않습니다**(`.gitignore` 처리됨).
65
+ 빌드 시점에 넣어 주세요.
66
+
67
+ **③ 실행**
68
+
69
+ ```bash
70
+ npm start # Expo 개발 서버
71
+ npm run android # Android 실행
72
+ npm run build:apk:android # Metro 없이 도는 Standalone APK
73
+ ```
74
+
75
+ 자세한 내용은 클론된 프로젝트의 `README.md` 와 `docs/` 를 보세요.
76
+
77
+ ## 사전 준비
78
+
79
+ - Node.js 18 이상, Git
80
+ - Android: JDK 17, Android Studio/SDK, `adb`
81
+ - iOS: macOS, Xcode, CocoaPods
82
+
83
+ ## 관련
84
+
85
+ - 템플릿 저장소: <https://github.com/insystem-dev01/insystem-shop-web-app>
86
+ - 백엔드 보일러플레이트: `create-insystem-nest-app`
package/index.js ADDED
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+
4
+ /**
5
+ * create-insystem-native-app
6
+ *
7
+ * INSYSTEM 하이브리드 앱 보일러플레이트(`insystem-shop-web-app`)를 클론해
8
+ * 새 고객사용 프로젝트를 만듭니다.
9
+ *
10
+ * `create-insystem-nest-app` 과 같은 구조입니다 — git clone 후 히스토리를 지우고
11
+ * 의존성을 설치합니다. 다른 점은 아래 두 가지입니다.
12
+ *
13
+ * 1. 기본 브랜치가 `template` 입니다. 이 저장소의 `main` 은 인시스템 마트 앱의
14
+ * 실제 배포본이라 다른 고객사가 그대로 쓰면 안 됩니다.
15
+ * 2. 클론 후 `.env.example` → `.env` 를 만들어 줍니다. 이 앱은 `.env` 없이는
16
+ * WebView 주소를 몰라 실행 자체가 되지 않습니다.
17
+ */
18
+
19
+ const { execSync, spawnSync } = require('child_process');
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+
23
+ const DEFAULT_REPO =
24
+ 'https://github.com/insystem-dev01/insystem-shop-web-app.git';
25
+
26
+ /**
27
+ * 🚨 기본 브랜치를 `main` 이 아니라 `template` 로 둡니다.
28
+ * `main` 에는 마트 앱의 브랜드·식별자가 들어 있어 새 고객사 시작점으로 맞지 않습니다.
29
+ */
30
+ const DEFAULT_BRANCH = 'template';
31
+
32
+ function printHelpAndExit(exitCode = 0) {
33
+ console.log(`
34
+ Usage:
35
+ create-insystem-native-app <target-dir> [options]
36
+ create-insystem-native-app -h | --help
37
+
38
+ Options:
39
+ --branch <branch> 클론할 브랜치 (기본: ${DEFAULT_BRANCH})
40
+ --repo <url> 템플릿 git 주소 (기본: 인시스템 하이브리드 앱 보일러플레이트)
41
+ --no-install 의존성 설치 건너뛰기
42
+ --no-env .env.example 복사 건너뛰기
43
+ --git 클론 후 새 git 저장소로 초기화
44
+ -h, --help 도움말
45
+
46
+ Examples:
47
+ npx create-insystem-native-app ./my-shop-app
48
+ npx create-insystem-native-app ./my-shop-app --branch main
49
+ npx create-insystem-native-app ./my-shop-app --no-install
50
+ npx create-insystem-native-app ./my-shop-app --git
51
+ `);
52
+ process.exit(exitCode);
53
+ }
54
+
55
+ function hasCmd(cmd) {
56
+ try {
57
+ execSync(
58
+ process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`,
59
+ { stdio: 'ignore' },
60
+ );
61
+ return true;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ function parseArgs(argv) {
68
+ const args = {
69
+ repo: DEFAULT_REPO,
70
+ branch: DEFAULT_BRANCH,
71
+ noInstall: false,
72
+ noEnv: false,
73
+ initGit: false,
74
+ };
75
+ const rest = [];
76
+
77
+ for (let i = 2; i < argv.length; i++) {
78
+ const a = argv[i];
79
+ if (a === '--branch') {
80
+ args.branch = argv[++i];
81
+ } else if (a === '--repo') {
82
+ args.repo = argv[++i];
83
+ } else if (a === '--no-install') {
84
+ args.noInstall = true;
85
+ } else if (a === '--no-env') {
86
+ args.noEnv = true;
87
+ } else if (a === '--git') {
88
+ args.initGit = true;
89
+ } else if (a === '--no-git') {
90
+ args.initGit = false;
91
+ } else if (a === '-h' || a === '--help') {
92
+ printHelpAndExit(0);
93
+ } else {
94
+ rest.push(a);
95
+ }
96
+ }
97
+
98
+ if (rest.length < 1) printHelpAndExit(1);
99
+ args.target = rest[0];
100
+ return args;
101
+ }
102
+
103
+ function ensureDirReady(targetAbs) {
104
+ if (fs.existsSync(targetAbs)) {
105
+ const stat = fs.statSync(targetAbs);
106
+ if (!stat.isDirectory()) {
107
+ console.error(`❌ 대상이 폴더가 아닙니다: ${targetAbs}`);
108
+ process.exit(1);
109
+ }
110
+ const files = fs
111
+ .readdirSync(targetAbs)
112
+ .filter((f) => f !== '.git' && f !== '.gitkeep');
113
+ if (files.length > 0) {
114
+ console.error(`❌ 대상 폴더가 비어 있지 않습니다: ${targetAbs}`);
115
+ console.error(' 빈 폴더 또는 새 경로를 지정하세요.');
116
+ process.exit(1);
117
+ }
118
+ } else {
119
+ fs.mkdirSync(targetAbs, { recursive: true });
120
+ }
121
+ }
122
+
123
+ function removeGitFolder(targetAbs) {
124
+ const gitPath = path.join(targetAbs, '.git');
125
+ if (fs.existsSync(gitPath)) {
126
+ fs.rmSync(gitPath, { recursive: true, force: true });
127
+ }
128
+ }
129
+
130
+ /**
131
+ * `.env.example` → `.env`
132
+ *
133
+ * 이 앱은 `EXPO_PUBLIC_WEB_APP_URL` 이 없으면 띄울 웹 주소를 모릅니다.
134
+ * 값 자체는 고객사마다 다르므로 예시를 그대로 복사만 하고, 수정은 사용자가 합니다.
135
+ */
136
+ function copyEnvExample(targetAbs) {
137
+ const example = path.join(targetAbs, '.env.example');
138
+ const dest = path.join(targetAbs, '.env');
139
+
140
+ if (!fs.existsSync(example)) return false;
141
+ if (fs.existsSync(dest)) return false;
142
+
143
+ fs.copyFileSync(example, dest);
144
+ return true;
145
+ }
146
+
147
+ function detectPkgManager() {
148
+ const ua = process.env.npm_config_user_agent || '';
149
+ if (ua.startsWith('pnpm/')) return 'pnpm';
150
+ if (ua.startsWith('yarn/')) return 'yarn';
151
+ if (ua.startsWith('bun/')) return 'bun';
152
+ if (ua.startsWith('npm/')) return 'npm';
153
+
154
+ if (hasCmd('pnpm')) return 'pnpm';
155
+ if (hasCmd('yarn')) return 'yarn';
156
+ if (hasCmd('bun')) return 'bun';
157
+ return 'npm';
158
+ }
159
+
160
+ /** Windows 에서 npm/yarn/pnpm 은 .cmd 스크립트라 shell 이 필요합니다. */
161
+ function getPkgManagerRunArgs(pm) {
162
+ const isWin = process.platform === 'win32';
163
+ const cmd = isWin
164
+ ? { npm: 'npm.cmd', yarn: 'yarn.cmd', pnpm: 'pnpm.cmd', bun: 'bun' }[pm] || pm
165
+ : pm;
166
+ const args = pm === 'yarn' ? [] : ['install'];
167
+ return { cmd, args, shell: isWin };
168
+ }
169
+
170
+ function run(cmd, args, opts = {}) {
171
+ const res = spawnSync(cmd, args, { stdio: 'inherit', shell: false, ...opts });
172
+ if (res.status !== 0) {
173
+ console.error(`❌ 명령 실패: ${cmd} ${args.join(' ')}`);
174
+ process.exit(res.status || 1);
175
+ }
176
+ }
177
+
178
+ (function main() {
179
+ const args = parseArgs(process.argv);
180
+
181
+ if (!hasCmd('git')) {
182
+ console.error('❌ Git 이 설치되어 있지 않거나 PATH 에 없습니다.');
183
+ process.exit(1);
184
+ }
185
+
186
+ const targetAbs = path.resolve(process.cwd(), args.target);
187
+ ensureDirReady(targetAbs);
188
+
189
+ // 1) 클론
190
+ console.log(`🚚 템플릿을 클론합니다... (${args.branch})`);
191
+ const cloneArgs = ['clone', '--depth', '1', '-b', args.branch, args.repo, targetAbs];
192
+ run('git', cloneArgs);
193
+
194
+ // 2) 히스토리 제거 — 새 프로젝트는 자기 히스토리로 시작합니다
195
+ removeGitFolder(targetAbs);
196
+
197
+ // 3) .env 준비
198
+ let envCreated = false;
199
+ if (!args.noEnv) {
200
+ envCreated = copyEnvExample(targetAbs);
201
+ if (envCreated) console.log('📝 .env.example 을 .env 로 복사했습니다.');
202
+ }
203
+
204
+ // 4) git 초기화 (--git 일 때만)
205
+ if (args.initGit) {
206
+ console.log('🔧 새 Git 저장소를 초기화합니다...');
207
+ run('git', ['init'], { cwd: targetAbs });
208
+ run('git', ['add', '.'], { cwd: targetAbs });
209
+ run('git', ['commit', '-m', 'chore: init from insystem hybrid app template'], {
210
+ cwd: targetAbs,
211
+ });
212
+ run('git', ['branch', '-M', 'main'], { cwd: targetAbs });
213
+ }
214
+
215
+ // 5) 의존성 설치
216
+ const pm = detectPkgManager();
217
+ if (!args.noInstall) {
218
+ console.log(`📦 ${pm} 로 의존성을 설치합니다...`);
219
+ const { cmd, args: pmArgs, shell } = getPkgManagerRunArgs(pm);
220
+ run(cmd, pmArgs, { cwd: targetAbs, shell });
221
+ } else {
222
+ console.log('⏭ 의존성 설치를 건너뜁니다 (--no-install).');
223
+ }
224
+
225
+ // 6) 다음 단계 안내
226
+ const shownPath = path.relative(process.cwd(), targetAbs) || '.';
227
+ console.log('\n✅ 완료!');
228
+ console.log('\n다음 단계:');
229
+ console.log(` cd ${shownPath}`);
230
+ if (args.noInstall) console.log(` ${pm} install`);
231
+
232
+ console.log('\n 1) .env 를 고객사에 맞게 수정하세요');
233
+ if (!envCreated && !args.noEnv) {
234
+ console.log(' ⚠️ .env 가 이미 있거나 .env.example 이 없어 복사하지 않았습니다.');
235
+ }
236
+ console.log(' EXPO_PUBLIC_WEB_APP_URL WebView 로 띄울 웹 주소 (필수)');
237
+ console.log(' APP_DISPLAY_NAME / APP_SCHEME');
238
+ console.log(' ANDROID_PACKAGE / IOS_BUNDLE_IDENTIFIER');
239
+ console.log('\n 2) 푸시를 쓴다면 고객사 Firebase 파일을 넣으세요');
240
+ console.log(' config/firebase/google-services.json (저장소에 커밋되지 않습니다)');
241
+ console.log('\n 3) 실행');
242
+ console.log(` ${pm} start Expo 개발 서버`);
243
+ console.log(` ${pm} run android Android 실행`);
244
+ console.log(` ${pm} run build:apk:android Standalone APK 빌드`);
245
+ console.log('\n 자세한 내용은 클론된 프로젝트의 README.md 를 보세요.');
246
+ })();
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "create-insystem-native-app",
3
+ "version": "0.0.1",
4
+ "description": "Clone the INSYSTEM hybrid app (Expo + WebView) boilerplate and bootstrap a new project",
5
+ "bin": {
6
+ "create-insystem-native-app": "index.js"
7
+ },
8
+ "files": [
9
+ "index.js",
10
+ "README.md"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "eastzoo",
14
+ "homepage": "https://github.com/insystem-dev01/insystem-shop-web-app",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/insystem-dev01/insystem-shop-web-app.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/insystem-dev01/insystem-shop-web-app/issues"
21
+ },
22
+ "engines": {
23
+ "node": ">=18.0.0"
24
+ },
25
+ "keywords": [
26
+ "create",
27
+ "scaffold",
28
+ "boilerplate",
29
+ "expo",
30
+ "react-native",
31
+ "webview",
32
+ "hybrid",
33
+ "insystem",
34
+ "eastzoo"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }