create-lesa-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.
- package/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.ko.md +115 -0
- package/README.md +120 -0
- package/bin/create-lesa-app.mjs +9 -0
- package/package.json +46 -0
- package/src/apply.ts +93 -0
- package/src/assets/intro.json +1 -0
- package/src/copy.ts +81 -0
- package/src/create.ts +132 -0
- package/src/derive.ts +66 -0
- package/src/fetch-template.ts +79 -0
- package/src/flow.test.mjs +98 -0
- package/src/flow.ts +131 -0
- package/src/index.tsx +212 -0
- package/src/intro.tsx +45 -0
- package/src/ui.tsx +326 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
[Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) · [Semantic Versioning](https://semver.org/lang/ko/)
|
|
4
|
+
|
|
5
|
+
이 CLI 의 버전은 템플릿 버전과 **별개**다. 받아올 템플릿 태그는
|
|
6
|
+
`src/fetch-template.ts` 의 `TEMPLATE_REF` 에 고정돼 있다 — 템플릿이 릴리즈되면 그 값을
|
|
7
|
+
올려 다시 발행해야 새 템플릿이 나간다.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [0.0.1] — 2026-09-11
|
|
12
|
+
|
|
13
|
+
첫 공개 릴리즈. **PoC** 다 — `0.0.x` 는 안정성을 약속하지 않는다.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- 질문 두세 개(앱 이름 → 필요 시 slug → 홈 화면 이름 → Apple Team ID)로 프로젝트 생성
|
|
18
|
+
- slug 하나에서 `name`·`scheme`·`bundleId`·`package`·`version` 파생.
|
|
19
|
+
Android 패키지는 하이픈을 못 쓰므로 리버스 도메인에서만 제거한다
|
|
20
|
+
- 템플릿을 GitHub tarball(`TEMPLATE_REF` 고정)에서 받는다. 로컬 사본이 있으면 그걸 쓴다 —
|
|
21
|
+
`--template <path>` → `$LESA_TEMPLATE_DIR` → 형제 폴더 → GitHub
|
|
22
|
+
- 복사 대상은 템플릿의 `git ls-files` — 산출물·로컬 상태를 정의상 제외한다
|
|
23
|
+
- 잔여 자리표시 검사(줄번호 출력) · 실패 시 생성 폴더 정리 · 초기 커밋
|
|
24
|
+
- `--help` · `--version` · 알 수 없는 플래그 거부
|
|
25
|
+
- 스텝 머신을 UI 밖(`src/flow.ts`)에 둬서 TTY 없이 `pnpm test` 로 전 구간 검증
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
[unreleased]: https://github.com/LESANF/create-lesa-app/compare/v0.0.1...HEAD
|
|
30
|
+
[0.0.1]: https://github.com/LESANF/create-lesa-app/releases/tag/v0.0.1
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LESANF
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# create-lesa-app
|
|
4
|
+
|
|
5
|
+
lesa Expo 템플릿 발사대.<br/>
|
|
6
|
+
slug 하나만 주면 스킴·번들 ID·Android 패키지·첫 커밋까지 나온다.
|
|
7
|
+
|
|
8
|
+
[](https://docs.expo.dev/versions/v57.0.0/)
|
|
9
|
+
[](https://reactnative.dev)
|
|
10
|
+
[](https://docs.expo.dev/router/introduction/)
|
|
11
|
+
[](https://docs.swmansion.com/react-native-reanimated/)<br/>
|
|
12
|
+
[](https://uniwind.dev)
|
|
13
|
+
[](https://tanstack.com/query)
|
|
14
|
+
[](https://zustand.docs.pmnd.rs)
|
|
15
|
+
[](https://www.typescriptlang.org)
|
|
16
|
+
|
|
17
|
+
[English](README.md) · [한국어](README.ko.md)
|
|
18
|
+
|
|
19
|
+
**⚗️ 전부 실험적입니다. 저(LESA)를 위해 만들었습니다.**
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
> [!WARNING]
|
|
24
|
+
> **이건 제 것입니다.** 제 취향·제 업무 흐름에 맞춰 만들었고 모든 선택이 실험적입니다.
|
|
25
|
+
> 안정성도 하위 호환도 지원도 약속하지 않고, 예고 없이 구조가 바뀝니다.
|
|
26
|
+
>
|
|
27
|
+
> 읽고 참고하시는 건 환영합니다. 다만 **그대로 쓰지 말고 직접 검증하세요.**
|
|
28
|
+
|
|
29
|
+
<br/>
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx create-lesa-app my-new-app
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
┌ create-lesa-app
|
|
37
|
+
│
|
|
38
|
+
│ ◇ App name
|
|
39
|
+
│ lesa-app
|
|
40
|
+
│
|
|
41
|
+
│ ◇ Display name
|
|
42
|
+
│ Lesa App
|
|
43
|
+
│
|
|
44
|
+
│ ◆ Apple Team ID
|
|
45
|
+
│ ╭────────────────────────────────────────────────╮
|
|
46
|
+
│ │ › ▌ │
|
|
47
|
+
│ ╰────────────────────────────────────────────────╯
|
|
48
|
+
│ optional · iOS only · Enter to skip
|
|
49
|
+
│
|
|
50
|
+
└ Enter skip or continue · Esc cancel
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> [!NOTE]
|
|
54
|
+
> 템플릿은
|
|
55
|
+
> [`LESANF/react-native-template-lesa`](https://github.com/LESANF/react-native-template-lesa)
|
|
56
|
+
> 의 고정 태그에서 받는다 — 오늘과 내일 같은 명령이 같은 프로젝트를 만든다.
|
|
57
|
+
> 로컬 사본이 있으면 그걸 쓴다. 순서는
|
|
58
|
+
> `--template <path>` → `$LESA_TEMPLATE_DIR` → 형제 `lesa-expo-template` → GitHub.
|
|
59
|
+
|
|
60
|
+
<br/>
|
|
61
|
+
|
|
62
|
+
## slug 하나에서 나오는 것
|
|
63
|
+
|
|
64
|
+
| 필드 | development | preview | production |
|
|
65
|
+
| ---------------------- | ------------------------- | --------------------- | ------------- |
|
|
66
|
+
| `name` · `slug` | `<slug>` — 전 환경 공통 | | |
|
|
67
|
+
| `scheme` | `<slug>-dev` | `<slug>-preview` | `<slug>` |
|
|
68
|
+
| `bundleId` · `package` | `com.<slug*>.development` | `com.<slug*>.preview` | `com.<slug*>` |
|
|
69
|
+
| 버전 | `0.0.1` build `1` | | |
|
|
70
|
+
|
|
71
|
+
`<slug*>` 는 하이픈을 뺀 값이다. Android 패키지명은 점 사이에 문자·숫자·밑줄만 허용해서
|
|
72
|
+
`lesa-app` 이 `com.lesaapp` 이 된다. URL 스킴은 하이픈을 허용하니 그대로 둔다.
|
|
73
|
+
|
|
74
|
+
소문자 ASCII 가 아닌 이름은 표시명으로 쓰고 slug 을 따로 묻는다. 로마자로 변환하지 않는다.
|
|
75
|
+
이 값이 Xcode 프로젝트명·스킴·`PRODUCT_NAME` 이 되는데 변환은 잃는 게 너무 많다.
|
|
76
|
+
|
|
77
|
+
<br/>
|
|
78
|
+
|
|
79
|
+
## 묻지 않는 것
|
|
80
|
+
|
|
81
|
+
API·OTA 주소, 유니버설 링크 호스트, Android 서명, 아이콘, `firebase/` 파일. 생성 시점에
|
|
82
|
+
알 수 없다. 프로젝트가 채울 자리는 전부 `TODO(앱)` 으로 찍혀 있고 완료 화면이 그 `grep`
|
|
83
|
+
명령을 띄운다.
|
|
84
|
+
|
|
85
|
+
<br/>
|
|
86
|
+
|
|
87
|
+
## 알아둘 것
|
|
88
|
+
|
|
89
|
+
- **새 파일은 스테이징해야 한다.** 복사 대상은 `git ls-files` 다. 산출물과 로컬 상태가
|
|
90
|
+
정의상 빠지는 대신, 방금 만든 파일은 `git add` 전까지 복사되지 않는다.
|
|
91
|
+
- **`npm link` 는 Node 버전을 따라간다.** nvm 으로 갈아타면 다시 걸거나, 링크 없이 이
|
|
92
|
+
레포에서 `pnpm start ../my-new-app` 을 쓴다.
|
|
93
|
+
- **프롬프트를 TTY 없이 검증한다.** `useInput` 이 TTY 를 요구하므로 스텝 머신은
|
|
94
|
+
`src/flow.ts` 에 따로 뒀다. `pnpm test` 가 전 구간을 돌린다.
|
|
95
|
+
- **실패는 치우고 나간다.** 실패한 실행은 만든 디렉터리를 지운다. `Ctrl+C` 는 조용히
|
|
96
|
+
지우지 않고 남긴다.
|
|
97
|
+
|
|
98
|
+
<br/>
|
|
99
|
+
|
|
100
|
+
## 명령
|
|
101
|
+
|
|
102
|
+
| | |
|
|
103
|
+
| ----------------------- | ---------------------------- |
|
|
104
|
+
| `create-lesa-app <dir>` | 프로젝트 생성 |
|
|
105
|
+
| `--template <path>` | 템플릿 경로 직접 지정 |
|
|
106
|
+
| `--help` · `--version` | 사용법 · 버전 |
|
|
107
|
+
| `pnpm test` | 스텝 머신·파생 규칙 테스트 |
|
|
108
|
+
| `pnpm type-check` | `tsc --noEmit` |
|
|
109
|
+
| `pnpm bake-intro` | ASCII 워드마크 프레임 재생성 |
|
|
110
|
+
|
|
111
|
+
<br/>
|
|
112
|
+
|
|
113
|
+
## 라이선스
|
|
114
|
+
|
|
115
|
+
MIT — 마음대로 쓰세요.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# create-lesa-app
|
|
4
|
+
|
|
5
|
+
Launcher for the lesa Expo template.<br/>
|
|
6
|
+
One slug in — schemes, bundle ids, Android packages and a first commit out.
|
|
7
|
+
|
|
8
|
+
[](https://docs.expo.dev/versions/v57.0.0/)
|
|
9
|
+
[](https://reactnative.dev)
|
|
10
|
+
[](https://docs.expo.dev/router/introduction/)
|
|
11
|
+
[](https://docs.swmansion.com/react-native-reanimated/)<br/>
|
|
12
|
+
[](https://uniwind.dev)
|
|
13
|
+
[](https://tanstack.com/query)
|
|
14
|
+
[](https://zustand.docs.pmnd.rs)
|
|
15
|
+
[](https://www.typescriptlang.org)
|
|
16
|
+
|
|
17
|
+
[English](README.md) · [한국어](README.ko.md)
|
|
18
|
+
|
|
19
|
+
**⚗️ Everything here is experimental. Built for me (LESA).**
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
> [!WARNING]
|
|
24
|
+
> **This is mine.** It is shaped around my taste and my workflow, and every
|
|
25
|
+
> choice in it is experimental. No stability, no backwards compatibility, no
|
|
26
|
+
> support — the structure changes without notice.
|
|
27
|
+
>
|
|
28
|
+
> Read it, borrow from it. But **do not ship it as-is** — verify it yourself.
|
|
29
|
+
|
|
30
|
+
<br/>
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm install && npm link
|
|
34
|
+
create-lesa-app my-new-app
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
┌ create-lesa-app
|
|
39
|
+
│
|
|
40
|
+
│ ◇ App name
|
|
41
|
+
│ lesa-app
|
|
42
|
+
│
|
|
43
|
+
│ ◇ Display name
|
|
44
|
+
│ Lesa App
|
|
45
|
+
│
|
|
46
|
+
│ ◆ Apple Team ID
|
|
47
|
+
│ ╭────────────────────────────────────────────────╮
|
|
48
|
+
│ │ › ▌ │
|
|
49
|
+
│ ╰────────────────────────────────────────────────╯
|
|
50
|
+
│ optional · iOS only · Enter to skip
|
|
51
|
+
│
|
|
52
|
+
└ Enter skip or continue · Esc cancel
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> [!NOTE]
|
|
56
|
+
> The template is fetched from
|
|
57
|
+
> [`LESANF/react-native-template-lesa`](https://github.com/LESANF/react-native-template-lesa)
|
|
58
|
+
> at a pinned tag, so the same command gives the same project tomorrow.
|
|
59
|
+
> A local copy wins when present — lookup order is
|
|
60
|
+
> `--template <path>` → `$LESA_TEMPLATE_DIR` → a sibling `lesa-expo-template` → GitHub.
|
|
61
|
+
|
|
62
|
+
<br/>
|
|
63
|
+
|
|
64
|
+
## Derived from one slug
|
|
65
|
+
|
|
66
|
+
| field | development | preview | production |
|
|
67
|
+
| ---------------------- | ---------------------------- | --------------------- | ------------- |
|
|
68
|
+
| `name` · `slug` | `<slug>` — every environment | | |
|
|
69
|
+
| `scheme` | `<slug>-dev` | `<slug>-preview` | `<slug>` |
|
|
70
|
+
| `bundleId` · `package` | `com.<slug*>.development` | `com.<slug*>.preview` | `com.<slug*>` |
|
|
71
|
+
| version | `0.0.1` build `1` | | |
|
|
72
|
+
|
|
73
|
+
`<slug*>` drops hyphens. Android package names allow only letters, digits and
|
|
74
|
+
underscores between periods, so `lesa-app` becomes `com.lesaapp` — URL schemes
|
|
75
|
+
do allow hyphens and keep them.
|
|
76
|
+
|
|
77
|
+
A name that is not lowercase ASCII becomes the display name, and a slug is asked
|
|
78
|
+
separately. It is never romanized: that value ends up as the Xcode project name,
|
|
79
|
+
the scheme and `PRODUCT_NAME`, and transliteration loses too much.
|
|
80
|
+
|
|
81
|
+
<br/>
|
|
82
|
+
|
|
83
|
+
## Not asked
|
|
84
|
+
|
|
85
|
+
API and OTA urls, universal-link hosts, Android signing, icons, `firebase/`
|
|
86
|
+
files. None are knowable at creation time. Every spot the project has to fill in
|
|
87
|
+
is marked `TODO(앱)`, and the closing screen prints the `grep` for it.
|
|
88
|
+
|
|
89
|
+
<br/>
|
|
90
|
+
|
|
91
|
+
## Notes
|
|
92
|
+
|
|
93
|
+
- **Stage new template files.** Copying is driven by `git ls-files`, so build
|
|
94
|
+
output and local state are excluded by definition — and a file you just created
|
|
95
|
+
is not copied until it is `git add`ed.
|
|
96
|
+
- **`npm link` follows your Node version.** Re-run it after switching with nvm,
|
|
97
|
+
or skip linking and run `pnpm start ../my-new-app` from this repo.
|
|
98
|
+
- **The prompt is testable without a TTY.** `useInput` needs one, so the step
|
|
99
|
+
machine lives in `src/flow.ts` instead. `pnpm test` drives the whole flow.
|
|
100
|
+
- **Failures clean up.** A failed run deletes the directory it created. `Ctrl+C`
|
|
101
|
+
leaves a partial tree alone rather than removing it silently.
|
|
102
|
+
|
|
103
|
+
<br/>
|
|
104
|
+
|
|
105
|
+
## Commands
|
|
106
|
+
|
|
107
|
+
| | |
|
|
108
|
+
| ----------------------- | --------------------------------- |
|
|
109
|
+
| `create-lesa-app <dir>` | create a project |
|
|
110
|
+
| `--template <path>` | point at a template explicitly |
|
|
111
|
+
| `--help` · `--version` | usage · version |
|
|
112
|
+
| `pnpm test` | step machine and derivation tests |
|
|
113
|
+
| `pnpm type-check` | `tsc --noEmit` |
|
|
114
|
+
| `pnpm bake-intro` | re-bake the ASCII wordmark frames |
|
|
115
|
+
|
|
116
|
+
<br/>
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT — use it however you like.
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-lesa-app",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Create a new project from the lesa Expo SDK 57 template",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "tsx src/index.tsx",
|
|
7
|
+
"test": "node --experimental-strip-types --no-warnings src/flow.test.mjs",
|
|
8
|
+
"type-check": "tsc --noEmit",
|
|
9
|
+
"bake-intro": "node scripts/bake-intro.mjs"
|
|
10
|
+
},
|
|
11
|
+
"author": "LESANF",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"packageManager": "pnpm@10.29.3",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"ink": "^7.0.5",
|
|
16
|
+
"react": "^19.2.7",
|
|
17
|
+
"tsx": "^4.22.4"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^26.4.1",
|
|
21
|
+
"@types/react": "^19.2.17",
|
|
22
|
+
"typescript": "^6.0.3"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"create-lesa-app": "./bin/create-lesa-app.mjs"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"CHANGELOG.md",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"README.md",
|
|
32
|
+
"bin",
|
|
33
|
+
"src"
|
|
34
|
+
],
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/LESANF/create-lesa-app.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/LESANF/create-lesa-app/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/LESANF/create-lesa-app#readme",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=20"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/apply.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 생성된 프로젝트의 `env-candidates.ts` 자리표시를 치환하고 `.env` 를 만든다.
|
|
3
|
+
*
|
|
4
|
+
* 자리표시 문자열 치환이다 — AST·템플릿 엔진을 쓰지 않는다(사유: 템플릿 `docs/cli.md`).
|
|
5
|
+
* 치환 후 잔여 자리표시를 검사해서 남으면 실패로 본다: 템플릿의 자리표시가 바뀌었는데
|
|
6
|
+
* 여기를 안 고친 경우를 조용히 넘기지 않기 위한 장치다.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
|
|
12
|
+
import type { DerivedFields } from './derive.ts';
|
|
13
|
+
|
|
14
|
+
/** 템플릿 `env-candidates.ts` 의 자리표시 → 파생값. 순서가 중요하다(긴 것 먼저). */
|
|
15
|
+
function replacements(fields: DerivedFields): [string, string][] {
|
|
16
|
+
return [
|
|
17
|
+
// bundleId / package — 긴 접미사부터 치환해야 `write.your.bundlename` 이 먼저 먹지 않는다.
|
|
18
|
+
['write.your.bundlename.development', fields.bundleId.development],
|
|
19
|
+
['write.your.bundlename.preview', fields.bundleId.preview],
|
|
20
|
+
['write.your.bundlename', fields.bundleId.production],
|
|
21
|
+
// scheme
|
|
22
|
+
['write-your-scheme-dev', fields.scheme.development],
|
|
23
|
+
['write-your-scheme-preview', fields.scheme.preview],
|
|
24
|
+
['write-your-scheme', fields.scheme.production],
|
|
25
|
+
// identity
|
|
26
|
+
['write-your-app-name', fields.name],
|
|
27
|
+
['write-your-app-slug', fields.slug],
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const LEFTOVER = /write[-.]your/;
|
|
32
|
+
|
|
33
|
+
export class ApplyError extends Error {}
|
|
34
|
+
|
|
35
|
+
/** `env-candidates.ts` 치환. displayName 은 자리표시가 아니라 빈 문자열이라 따로 넣는다. */
|
|
36
|
+
export async function applyEnvCandidates(
|
|
37
|
+
projectDir: string,
|
|
38
|
+
fields: DerivedFields
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
const file = path.join(projectDir, 'env-candidates.ts');
|
|
41
|
+
let source: string;
|
|
42
|
+
try {
|
|
43
|
+
source = await readFile(file, 'utf8');
|
|
44
|
+
} catch {
|
|
45
|
+
throw new ApplyError(`Could not find env-candidates.ts: ${file}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const [from, to] of replacements(fields)) {
|
|
49
|
+
source = source.replaceAll(from, to);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (fields.displayName) {
|
|
53
|
+
const before = source;
|
|
54
|
+
source = source.replace("displayName: '',", `displayName: '${fields.displayName}',`);
|
|
55
|
+
if (source === before) {
|
|
56
|
+
throw new ApplyError("Could not find the displayName slot (`displayName: '',`).");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const leftover = source
|
|
61
|
+
.split('\n')
|
|
62
|
+
.map((line, index) => [index + 1, line] as const)
|
|
63
|
+
.filter(([, line]) => LEFTOVER.test(line));
|
|
64
|
+
if (leftover.length > 0) {
|
|
65
|
+
throw new ApplyError(
|
|
66
|
+
`Placeholders were left unreplaced — the template may have changed:\n` +
|
|
67
|
+
leftover.map(([n, line]) => ` env-candidates.ts:${n} ${line.trim()}`).join('\n')
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await writeFile(file, source);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Apple Team ID 를 받았으면 `.env` 를 미리 만든다.
|
|
76
|
+
* 안 받았으면 아무것도 하지 않는다 — 템플릿의 postinstall 이 `.env.example` 에서 만든다.
|
|
77
|
+
*/
|
|
78
|
+
export async function applyEnvFile(projectDir: string, appleTeamId: string): Promise<void> {
|
|
79
|
+
if (!appleTeamId) return;
|
|
80
|
+
|
|
81
|
+
const example = path.join(projectDir, '.env.example');
|
|
82
|
+
const target = path.join(projectDir, '.env');
|
|
83
|
+
const source = await readFile(example, 'utf8');
|
|
84
|
+
|
|
85
|
+
// .env.example 은 그 줄을 주석으로 갖고 있다 — 주석을 벗기고 값을 채운다.
|
|
86
|
+
const line = `APP_BUILD_ONLY_APPLE_TEAM_ID=${appleTeamId}`;
|
|
87
|
+
const replaced = source.replace(/^# APP_BUILD_ONLY_APPLE_TEAM_ID=$/m, line);
|
|
88
|
+
if (replaced === source) {
|
|
89
|
+
throw new ApplyError('Could not find the APP_BUILD_ONLY_APPLE_TEAM_ID line in .env.example.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await writeFile(target, replaced);
|
|
93
|
+
}
|