@apps-in-toss/cli 3.1.0-beta.0 → 3.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.
- package/CHANGELOG.md +76 -0
- package/LICENSE +201 -28
- package/dist/index.js +484 -45
- package/package.json +4 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @apps-in-toss/cli
|
|
2
|
+
|
|
3
|
+
## 3.1.0
|
|
4
|
+
|
|
5
|
+
## 3.0.5
|
|
6
|
+
|
|
7
|
+
## 3.0.4
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 6e935ed: - webView.pullToRefreshEnabled 기본값을 false로 변경해요.
|
|
12
|
+
- ait --help를 개선해요 (ko/en, --version, 컬러/라우팅 정리).
|
|
13
|
+
- 빌드 시 appName·brand·webBundle validation을 추가해요.
|
|
14
|
+
- 라이선스를 Apache-2.0으로 변경해요.
|
|
15
|
+
- 99d1861: fix: `--help` 표면 전반을 정비해요.
|
|
16
|
+
|
|
17
|
+
- help 출력의 바이너리 이름과 모든 예시를 실제 bin인 `ait`로 통일하고, `ait --version`을 지원해요. `deploy`·`init`·`build`·`migrate`·`token` 각 커맨드의 설명과 details를 실제 동작 기준으로 보강해요(API 키 해석 순서, `migrate v3`가 수행하는 변환 내용, 토큰 저장 위치 등).
|
|
18
|
+
- 컬러 미지원 환경(비-TTY 파이프·`NO_COLOR`·dumb 터미널)에서 CLI 출력에 ANSI 코드를 남기지 않아요 — clipanion이 렌더링하는 `--help`·에러 표면 포함.
|
|
19
|
+
- `--help`를 한국어와 영어로 제공해요. `AIT_LANG` 환경변수(`ko`/`en`)를 최우선으로 보고, 없으면 `LC_ALL` → `LC_CTYPE` → `LANG` 순으로 감지해요. 출력 끝에 반대편 언어로 전환하는 방법을 안내하는 힌트 한 줄을 추가해요.
|
|
20
|
+
- help 요청이 어떤 형태로 오든(`ait <command> --help`/`-h`, `ait help <command>`, `ait token` 그룹 help) 같은 스타일과 힌트 줄로 출력하도록 하나의 라우터로 통일하고, `token`에서 clipanion의 "Multiple commands match your selection" 목록이 뜨던 문제를 없앴어요. `ait help <존재하지 않는 명령어>`는 스타일드 에러와 함께 종료 코드 1로 끝나요.
|
|
21
|
+
|
|
22
|
+
- 732a23a: validation을 추가합니다. (app-name, brand, webBundle)
|
|
23
|
+
|
|
24
|
+
## 3.0.3
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- e068bb8: fix: 빌드 아티팩트의 `sdkVersion`이 실제 설치된 web-framework 버전과 어긋나던 문제를 고쳐요.
|
|
29
|
+
|
|
30
|
+
- web-framework가 의존하는 `@apps-in-toss/cli` 버전을 캐럿 범위 대신 정확한 버전으로 고정해요.
|
|
31
|
+
- CLI가 아티팩트 메타데이터에 자신의 버전 대신, 프로젝트에 실제 설치된 `@apps-in-toss/web-framework`의 버전을 기록해요. web-framework 없이 CLI만 쓰는 프로젝트에서는 기존처럼 CLI 버전을 남겨요.
|
|
32
|
+
|
|
33
|
+
## 3.0.2
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- 5519b6f: feat: `@apps-in-toss/devtools` 패키지를 추가해요 — 브라우저에서 미니앱을 개발할 수 있는 mock SDK, devtools 패널, 번들러 unplugin(vite/webpack/rspack/rollup/esbuild)을 제공해요. `ait init`과 `ait migrate v3`가 devtools를 devDependencies에 추가하고, 번들러 설정의 plugins 맨 앞에 unplugin을 자동으로 꽂은 뒤 의존성 설치까지 실행해요.
|
|
38
|
+
|
|
39
|
+
## 3.0.1
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- 2c9ac93: V3 레퍼런스 추가
|
|
44
|
+
|
|
45
|
+
## 3.0.0
|
|
46
|
+
|
|
47
|
+
### Major Changes
|
|
48
|
+
|
|
49
|
+
- 134f42c: 3.0.0 릴리즈 — granite 아키텍처(브릿지 SSoT 이전, 파일 라우터 공개 API, 문서 파이프라인)
|
|
50
|
+
|
|
51
|
+
### Patch Changes
|
|
52
|
+
|
|
53
|
+
- 975049b: migrate v3 커맨드를 고쳐요. TypeScript 설정 파일 파싱 실패를 수정하고, 모든 조건을 검증한 뒤 트랜잭션으로 실행하도록 바꿔요. 마이그레이션이 끝나면 granite.config를 삭제하고 유의사항을 안내해요.
|
|
54
|
+
- 245f18d: 라이센스 추가
|
|
55
|
+
|
|
56
|
+
## 3.0.0-rc.4
|
|
57
|
+
|
|
58
|
+
### Patch Changes
|
|
59
|
+
|
|
60
|
+
- 245f18d: 라이센스 추가
|
|
61
|
+
|
|
62
|
+
## 3.0.0-rc.3
|
|
63
|
+
|
|
64
|
+
## 3.0.0-rc.2
|
|
65
|
+
|
|
66
|
+
### Patch Changes
|
|
67
|
+
|
|
68
|
+
- 975049b: migrate v3 커맨드를 고쳐요. TypeScript 설정 파일 파싱 실패를 수정하고, 모든 조건을 검증한 뒤 트랜잭션으로 실행하도록 바꿔요. 마이그레이션이 끝나면 granite.config를 삭제하고 유의사항을 안내해요.
|
|
69
|
+
|
|
70
|
+
## 3.0.0-rc.1
|
|
71
|
+
|
|
72
|
+
## 3.0.0-rc.0
|
|
73
|
+
|
|
74
|
+
### Major Changes
|
|
75
|
+
|
|
76
|
+
- 3.0.0 릴리즈 — granite 아키텍처(브릿지 SSoT 이전, 파일 라우터 공개 API, 문서 파이프라인)
|
package/LICENSE
CHANGED
|
@@ -1,28 +1,201 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Viva Republica, Inc.
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { Builtins, Cli, Command, Option } from "clipanion";
|
|
3
|
+
import { Builtins, Cli, Command, Option, formatMarkdownish } from "clipanion";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { appendFile, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -10,10 +10,251 @@ import { loadConfig } from "c12";
|
|
|
10
10
|
import { uuidv7 } from "uuidv7";
|
|
11
11
|
import fs, { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import assert from "node:assert";
|
|
13
|
+
import pc from "picocolors";
|
|
13
14
|
import os from "node:os";
|
|
14
15
|
import { setTimeout } from "node:timers/promises";
|
|
15
16
|
import jscodeshift from "jscodeshift";
|
|
16
17
|
import { spawn } from "node:child_process";
|
|
18
|
+
//#region src/i18n/en.ts
|
|
19
|
+
const en = {
|
|
20
|
+
"groupHelp.commandsHeader": "Commands",
|
|
21
|
+
"groupHelp.detailHint": "You can also print more details about any of these commands by calling them with the -h,--help flag right after the command name.",
|
|
22
|
+
"help.unknownPath": "Couldn't find the '{path}' command.",
|
|
23
|
+
"help.unknownPath.guide": "See the available commands below.",
|
|
24
|
+
"build.description": "Reads the config file (apps-in-toss.config) and builds a deployable .ait artifact.",
|
|
25
|
+
"build.details": `
|
|
26
|
+
Reads apps-in-toss.config.(ts|js|mjs) from the project root (the directory containing package.json) and builds a .ait artifact. This fails if that config file doesn't exist — create one first with \`ait init\` for a new project, or \`ait migrate v3\` for an existing granite.config project.
|
|
27
|
+
|
|
28
|
+
The directory set as webBundleDir in apps-in-toss.config is packed into the artifact as-is, so before running this command you need to run your web framework's build command (e.g. vite build) so that directory has build output that includes index.html.
|
|
29
|
+
|
|
30
|
+
The finished artifact is saved as \`<appName>.ait\` in the project root, and \`ait deploy\` finds and uploads this file.
|
|
31
|
+
`,
|
|
32
|
+
"build.example.artifact.label": "Build artifact",
|
|
33
|
+
"deploy.description": "Uploads (deploys) a built .ait artifact to Apps in Toss.",
|
|
34
|
+
"deploy.details": `
|
|
35
|
+
The API key is resolved in this order:
|
|
36
|
+
|
|
37
|
+
- If a token is saved for the profile named by \`--profile\` (or \`default\` when not given), that token is used.
|
|
38
|
+
- If no saved token exists, the value passed directly via \`--api-key\` is used.
|
|
39
|
+
- If neither is available, you're prompted interactively for the API key.
|
|
40
|
+
|
|
41
|
+
To save a token for a profile ahead of time, register it first with \`ait token add\`.
|
|
42
|
+
`,
|
|
43
|
+
"deploy.example.default.label": "Deploy the default artifact (.ait)",
|
|
44
|
+
"deploy.example.location.label": "Deploy a specific .ait file",
|
|
45
|
+
"deploy.example.profile.label": "Deploy using a token saved in a profile",
|
|
46
|
+
"deploy.example.memo.label": "Deploy with release notes",
|
|
47
|
+
"deploy.example.memo.cmd": "ait deploy -m \"Release notes (up to 1000 chars)\"",
|
|
48
|
+
"deploy.example.schemeOnly.label": "Print only the deployment result scheme",
|
|
49
|
+
"deploy.option.apiKey": "The API key to use for deployment. Either pass it directly with this option, or use --profile to specify a profile and use the API key registered for it.",
|
|
50
|
+
"deploy.option.workspace": "(deprecated) The token workspace name. Use --profile instead.",
|
|
51
|
+
"deploy.option.profile": "A profile registered in ~/.ait/credentials via the ait token add command. Falls back to the default profile if none is given.",
|
|
52
|
+
"deploy.option.baseUrl": "Directly overrides the base URL of the deploy API. Not needed for a normal deploy — only used when testing against a separate environment.",
|
|
53
|
+
"deploy.option.location": "Explicitly sets the path to the .ait file to upload. If omitted, uses the first .ait file found in the current package root directory.",
|
|
54
|
+
"deploy.option.schemeOnly": "Makes the deploy print only the intoss-private scheme for the result. Defaults to false.",
|
|
55
|
+
"deploy.option.memo": "Sets the release notes. Up to 1000 characters.",
|
|
56
|
+
"init.description": "Adds the settings needed to deploy to Apps in Toss to an existing web project.",
|
|
57
|
+
"init.details": `
|
|
58
|
+
Doesn't create a new project — run this inside an existing web project that has a package.json (e.g. one created with Vite).
|
|
59
|
+
|
|
60
|
+
Running it does the following:
|
|
61
|
+
|
|
62
|
+
- Asks for an app name (kebab-case) and the web bundle output directory (default: dist).
|
|
63
|
+
- Creates an apps-in-toss.config.ts file.
|
|
64
|
+
- If package.json has a build script, appends \`&& ait build\` to it, and adds a \`deploy\` (\`ait deploy\`) script.
|
|
65
|
+
- Adds @apps-in-toss/devtools to devDependencies, and finds your vite/webpack/rspack/rollup config file to automatically plug the devtools plugin into its plugins array (shows manual setup instructions if it can't be plugged in automatically).
|
|
66
|
+
- Adds \`*.ait\` to .gitignore, and installs dependencies with the detected package manager.
|
|
67
|
+
`,
|
|
68
|
+
"init.example.interactive.label": "Initialize interactively",
|
|
69
|
+
"init.example.appName.label": "Initialize with an app name",
|
|
70
|
+
"init.option.appName": "Sets the app name in kebab-case. If omitted, you're prompted interactively. e.g. my-miniapp",
|
|
71
|
+
"migration.description": "Automatically converts an @apps-in-toss/web-framework 2.x project to the 3.x config format.",
|
|
72
|
+
"migration.details": `
|
|
73
|
+
Selects which migration to run via the target argument. Currently only one target is registered: \`v3\`.
|
|
74
|
+
|
|
75
|
+
\`ait migrate v3\` converts granite.config.(ts|js|mjs) to apps-in-toss.config with the same extension.
|
|
76
|
+
|
|
77
|
+
- Removes every property under \`brand\` except \`primaryColor\`.
|
|
78
|
+
- Renames \`webViewProps\` to \`webView\`, and removes the \`type\` property inside it.
|
|
79
|
+
- Renames \`outdir\` to \`webBundleDir\`.
|
|
80
|
+
- Moves the \`web.commands.dev\`/\`web.commands.build\` values to the \`dev\`/\`build\` scripts in \`package.json\`, and appends \`&& ait build\` to the end of the \`build\` script.
|
|
81
|
+
- Adds \`@apps-in-toss/devtools\` to \`devDependencies\`, and finds your vite/webpack/rspack/rollup config file to automatically plug the devtools plugin into its plugins array (shows manual setup instructions if it can't be plugged in automatically).
|
|
82
|
+
- Once all of the above changes are done, deletes the original granite.config file and runs install with the detected package manager.
|
|
83
|
+
|
|
84
|
+
If granite.config can't be found, or web.commands.dev/build isn't a string, nothing is changed and the reason is reported. If the conversion fails partway through, the changes applied so far are automatically rolled back — but once it finishes successfully it can't be rolled back, so we recommend saving your current state (e.g. with a git commit) before running it.
|
|
85
|
+
`,
|
|
86
|
+
"migration.example.v3.label": "Migrate to web-framework 3.x",
|
|
87
|
+
"token.description": "Manages API tokens used for deployment, per profile.",
|
|
88
|
+
"token.details": `
|
|
89
|
+
A profile is an API token saved under a name in the ~/.ait/credentials file. Specify \`ait deploy --profile <name>\` to deploy using the token saved for that profile; omit --profile to use the default profile.
|
|
90
|
+
`,
|
|
91
|
+
"token.example.add.label": "Add a token",
|
|
92
|
+
"token.example.remove.label": "Remove a token",
|
|
93
|
+
"tokenAdd.description": "Adds a secret token. Tokens are stored per profile in ~/.ait/credentials, and can be loaded with ait deploy --profile.",
|
|
94
|
+
"tokenAdd.example.default.label": "Add a token under the default profile",
|
|
95
|
+
"tokenAdd.example.profile.label": "Add a token under a named profile",
|
|
96
|
+
"tokenAdd.example.apiKey.label": "Pass the API key directly as an option",
|
|
97
|
+
"tokenAdd.option.apiKey": "Sets the token value directly. If omitted, you're prompted interactively.",
|
|
98
|
+
"tokenRemove.description": "Removes a secret token stored in ~/.ait/credentials.",
|
|
99
|
+
"tokenRemove.example.default.label": "Remove the default profile's token",
|
|
100
|
+
"tokenRemove.example.profile.label": "Remove a token by profile name"
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/i18n/ko.ts
|
|
104
|
+
const ko = {
|
|
105
|
+
"groupHelp.commandsHeader": "Commands",
|
|
106
|
+
"groupHelp.detailHint": "각 명령어 뒤에 -h 또는 --help 옵션을 붙이면 더 자세한 설명을 볼 수 있어요.",
|
|
107
|
+
"help.unknownPath": "'{path}' 명령어를 찾을 수 없어요.",
|
|
108
|
+
"help.unknownPath.guide": "사용 가능한 명령어는 아래에서 확인해주세요.",
|
|
109
|
+
"build.description": "설정 파일(apps-in-toss.config)을 읽어 배포용 .ait 아티팩트를 만들어요.",
|
|
110
|
+
"build.details": `
|
|
111
|
+
프로젝트 루트(package.json이 있는 디렉토리)의 apps-in-toss.config.(ts|js|mjs)를 읽어서 .ait 아티팩트를 만들어요. 이 설정 파일이 없으면 실행에 실패해요 — 신규 프로젝트라면 \`ait init\`으로, 기존 granite.config 프로젝트라면 \`ait migrate v3\`로 먼저 만들어주세요.
|
|
112
|
+
|
|
113
|
+
apps-in-toss.config의 webBundleDir에 지정한 디렉토리를 그대로 아티팩트에 담기 때문에, 이 명령을 실행하기 전에 웹 프레임워크의 빌드 명령(예: vite build)을 먼저 실행해서 그 디렉토리에 index.html을 포함한 산출물이 있어야 해요.
|
|
114
|
+
|
|
115
|
+
완성된 아티팩트는 프로젝트 루트에 \`<appName>.ait\`로 저장되고, \`ait deploy\`가 이 파일을 찾아 업로드해요.
|
|
116
|
+
`,
|
|
117
|
+
"build.example.artifact.label": "아티팩트 빌드",
|
|
118
|
+
"deploy.description": "빌드된 .ait 아티팩트를 앱인토스에 업로드(배포)해요.",
|
|
119
|
+
"deploy.details": `
|
|
120
|
+
API 키는 다음 순서로 결정돼요.
|
|
121
|
+
|
|
122
|
+
- \`--profile\`로 지정한(또는 지정하지 않았을 때 \`default\`) 프로필에 저장된 토큰이 있으면 그걸 사용해요.
|
|
123
|
+
- 저장된 토큰이 없으면 \`--api-key\`로 직접 입력한 값을 사용해요.
|
|
124
|
+
- 둘 다 없으면 대화형 프롬프트로 API 키를 입력받아요.
|
|
125
|
+
|
|
126
|
+
프로필에 토큰을 저장해두고 싶다면 \`ait token add\`로 먼저 등록해주세요.
|
|
127
|
+
`,
|
|
128
|
+
"deploy.example.default.label": "기본 아티팩트(.ait) 배포",
|
|
129
|
+
"deploy.example.location.label": "특정 .ait 파일 배포",
|
|
130
|
+
"deploy.example.profile.label": "프로필에 저장된 토큰으로 배포",
|
|
131
|
+
"deploy.example.memo.label": "출시 메모와 함께 배포",
|
|
132
|
+
"deploy.example.memo.cmd": "ait deploy -m \"출시 메모(최대 1000자)\"",
|
|
133
|
+
"deploy.example.schemeOnly.label": "배포 결과 scheme만 출력",
|
|
134
|
+
"deploy.option.apiKey": "배포를 위한 API 키예요. 이 옵션으로 직접 입력하거나, --profile 옵션으로 프로필을 지정하면 그 프로필에 등록된 API 키를 사용해요.",
|
|
135
|
+
"deploy.option.workspace": "(deprecated) 토큰 워크스페이스 이름이에요. 이 옵션 대신 --profile 옵션을 사용해주세요.",
|
|
136
|
+
"deploy.option.profile": "ait token add 명령어로 ~/.ait/credentials에 등록한 프로필이에요. 프로필이 없으면 default 프로필을 사용해요.",
|
|
137
|
+
"deploy.option.baseUrl": "배포 API의 base URL을 직접 지정해요. 일반적인 배포에서는 지정할 필요가 없고, 별도 환경을 대상으로 테스트할 때만 사용해요.",
|
|
138
|
+
"deploy.option.location": "업로드할 .ait 파일의 경로를 명시적으로 지정해요. 지정하지 않으면 현재 패키지 루트 디렉토리에서 발견되는 첫 .ait 파일을 사용해요.",
|
|
139
|
+
"deploy.option.schemeOnly": "배포 결과를 intoss-private scheme만 출력하도록 설정해요. 기본값은 false예요.",
|
|
140
|
+
"deploy.option.memo": "출시 메모를 입력해요. 최대 1000자까지 입력할 수 있어요.",
|
|
141
|
+
"init.description": "기존 웹 프로젝트에 앱인토스 배포에 필요한 설정을 추가해요.",
|
|
142
|
+
"init.details": `
|
|
143
|
+
새 프로젝트를 만들지는 않아요 — package.json이 있는 기존 웹 프로젝트(예: Vite로 만든 프로젝트)에서 실행해주세요.
|
|
144
|
+
|
|
145
|
+
실행하면 다음을 처리해요.
|
|
146
|
+
|
|
147
|
+
- 앱 이름(케밥-케이스)을 물어보고, 웹 번들 결과물 디렉토리를 물어봐요(기본값 dist).
|
|
148
|
+
- apps-in-toss.config.ts 파일을 생성해요.
|
|
149
|
+
- package.json에 build 스크립트가 있으면 뒤에 \`&& ait build\`를 붙이고, \`deploy\`(\`ait deploy\`) 스크립트를 추가해요.
|
|
150
|
+
- devDependencies에 @apps-in-toss/devtools를 추가하고, vite/webpack/rspack/rollup 설정 파일을 찾아 plugins 배열에 devtools 플러그인을 자동으로 꽂아요(자동으로 못 꽂으면 수동 설치 안내를 보여줘요).
|
|
151
|
+
- .gitignore에 \`*.ait\`를 추가하고, 감지한 패키지 매니저로 의존성을 설치해요.
|
|
152
|
+
`,
|
|
153
|
+
"init.example.interactive.label": "대화형으로 초기화",
|
|
154
|
+
"init.example.appName.label": "앱 이름을 지정하여 초기화",
|
|
155
|
+
"init.option.appName": "앱 이름을 케밥-케이스로 지정해요. 생략하면 대화형 프롬프트로 입력받아요. 예) my-miniapp",
|
|
156
|
+
"migration.description": "@apps-in-toss/web-framework 2.x 프로젝트를 3.x 설정 형식으로 자동 변환해요.",
|
|
157
|
+
"migration.details": `
|
|
158
|
+
target 인자로 실행할 마이그레이션을 선택해요. 현재 등록된 타깃은 \`v3\` 하나예요.
|
|
159
|
+
|
|
160
|
+
\`ait migrate v3\`는 granite.config.(ts|js|mjs)를 같은 확장자의 apps-in-toss.config로 변환해요.
|
|
161
|
+
|
|
162
|
+
- \`brand\`에서 \`primaryColor\`를 제외한 나머지 속성은 지워요.
|
|
163
|
+
- \`webViewProps\`는 \`webView\`로 이름을 바꾸고, 그 안의 \`type\` 속성은 지워요.
|
|
164
|
+
- \`outdir\`은 \`webBundleDir\`로 이름을 바꿔요.
|
|
165
|
+
- \`web.commands.dev\`/\`web.commands.build\` 값을 \`package.json\`의 \`dev\`/\`build\` 스크립트로 옮기고, \`build\` 스크립트 끝에 \`&& ait build\`를 붙여요.
|
|
166
|
+
- \`devDependencies\`에 \`@apps-in-toss/devtools\`를 추가하고, vite/webpack/rspack/rollup 설정 파일을 찾아 plugins 배열에 devtools 플러그인을 자동으로 꽂아요(자동으로 못 꽂으면 수동 설치 안내를 보여줘요).
|
|
167
|
+
- 위 변경이 모두 끝나면 원본 granite.config 파일을 삭제하고, 감지한 패키지 매니저로 설치를 실행해요.
|
|
168
|
+
|
|
169
|
+
granite.config를 찾지 못했거나 web.commands.dev/build가 문자열로 없으면 아무것도 바꾸지 않고 원인을 알려줘요. 변환 도중 실패하면 그때까지 적용한 변경은 자동으로 되돌리지만, 성공적으로 끝난 뒤에는 되돌릴 수 없으니 실행 전에 git commit 등으로 현재 상태를 저장해두는 걸 권장해요.
|
|
170
|
+
`,
|
|
171
|
+
"migration.example.v3.label": "web-framework 3.x로 마이그레이션",
|
|
172
|
+
"token.description": "배포에 사용할 API 토큰을 프로필별로 관리해요.",
|
|
173
|
+
"token.details": `
|
|
174
|
+
프로필은 ~/.ait/credentials 파일에 이름으로 구분해서 저장하는 API 토큰이에요. \`ait deploy --profile <이름>\`으로 지정하면 그 프로필에 저장된 토큰을 사용해서 배포하고, --profile을 생략하면 default 프로필을 사용해요.
|
|
175
|
+
`,
|
|
176
|
+
"token.example.add.label": "토큰 추가",
|
|
177
|
+
"token.example.remove.label": "토큰 삭제",
|
|
178
|
+
"tokenAdd.description": "시크릿 토큰을 추가해요. 토큰은 ~/.ait/credentials에 프로필별로 저장되고, ait deploy --profile로 불러올 수 있어요.",
|
|
179
|
+
"tokenAdd.example.default.label": "기본 프로필로 토큰 추가",
|
|
180
|
+
"tokenAdd.example.profile.label": "프로필을 지정하여 토큰 추가",
|
|
181
|
+
"tokenAdd.example.apiKey.label": "API 키를 옵션으로 직접 전달",
|
|
182
|
+
"tokenAdd.option.apiKey": "토큰 값을 직접 지정해요. 생략하면 대화형 프롬프트로 입력받아요.",
|
|
183
|
+
"tokenRemove.description": "~/.ait/credentials에 저장된 시크릿 토큰을 삭제해요.",
|
|
184
|
+
"tokenRemove.example.default.label": "기본 프로필의 토큰 삭제",
|
|
185
|
+
"tokenRemove.example.profile.label": "프로필을 지정하여 토큰 삭제"
|
|
186
|
+
};
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/i18n/index.ts
|
|
189
|
+
/**
|
|
190
|
+
* i18n for the CLI's --help/usage layer only (command `description`/
|
|
191
|
+
* `details`/`examples`, `Option(...)` `description`). Runtime messages
|
|
192
|
+
* (errors, interactive prompts, progress output, migration logs) are NOT
|
|
193
|
+
* covered by this module and keep using their existing Korean strings
|
|
194
|
+
* directly in the command files.
|
|
195
|
+
*
|
|
196
|
+
* Locale is resolved ONCE per process, at module load time, from
|
|
197
|
+
* `process.env` — not re-detected per call — because clipanion's
|
|
198
|
+
* `Command.Usage()` bakes its strings into static class fields at class
|
|
199
|
+
* definition time, so the active locale has to be known before any command
|
|
200
|
+
* module is evaluated.
|
|
201
|
+
*
|
|
202
|
+
* Locale decision policy (maintainer-confirmed):
|
|
203
|
+
*
|
|
204
|
+
* 1. `AIT_LANG` env var, trimmed and lowercased. If it's `"ko"` or `"en"`,
|
|
205
|
+
* that value wins outright. Any other value is ignored and falls
|
|
206
|
+
* through to step 2.
|
|
207
|
+
* 2. Otherwise, check `LC_ALL` → `LC_CTYPE` → `LANG`, in that order, for
|
|
208
|
+
* the first non-empty value (an empty string counts as unset, per
|
|
209
|
+
* POSIX convention). If that value's language tag matches `ko`
|
|
210
|
+
* (`ko`, `ko_KR.UTF-8`, `ko-KR`, ... — anything starting with `ko`
|
|
211
|
+
* followed by `.`/`_`/`-`/end-of-string), the locale is `ko`.
|
|
212
|
+
* Everything else (`en_US.UTF-8`, `C`, `POSIX`, ...) resolves to `en`.
|
|
213
|
+
* 3. If all three are empty/unset, the locale is `en`.
|
|
214
|
+
*/
|
|
215
|
+
const KO_LANGUAGE_TAG_RE = /^ko([._-]|$)/i;
|
|
216
|
+
/**
|
|
217
|
+
* Pure decision function — takes `process.env` as an input instead of
|
|
218
|
+
* reading it directly, so the policy above is unit-testable without
|
|
219
|
+
* mutating global process state.
|
|
220
|
+
*/
|
|
221
|
+
function resolveLocale(env) {
|
|
222
|
+
const override = env.AIT_LANG?.trim().toLowerCase();
|
|
223
|
+
if (override === "ko" || override === "en") return override;
|
|
224
|
+
const firstNonEmpty = [
|
|
225
|
+
env.LC_ALL,
|
|
226
|
+
env.LC_CTYPE,
|
|
227
|
+
env.LANG
|
|
228
|
+
].find((value) => value != null && value !== "");
|
|
229
|
+
if (firstNonEmpty != null && KO_LANGUAGE_TAG_RE.test(firstNonEmpty)) return "ko";
|
|
230
|
+
return "en";
|
|
231
|
+
}
|
|
232
|
+
/** The active locale for this process, decided once at module load. */
|
|
233
|
+
const locale = resolveLocale(process.env);
|
|
234
|
+
const tables = {
|
|
235
|
+
ko,
|
|
236
|
+
en
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* Look up a help/usage string for the active locale, with `{name}`
|
|
240
|
+
* placeholder interpolation. Falls back to the key itself if missing, so a
|
|
241
|
+
* forgotten key surfaces visibly rather than rendering empty.
|
|
242
|
+
*/
|
|
243
|
+
function t(key, vars) {
|
|
244
|
+
const raw = tables[locale][key] ?? key;
|
|
245
|
+
if (!vars) return raw;
|
|
246
|
+
return raw.replace(/\{(\w+)\}/g, (match, name) => {
|
|
247
|
+
const value = vars[name];
|
|
248
|
+
return value === void 0 ? match : String(value);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
const HINT_WHEN_ACTIVE_IS_KO = "Set AIT_LANG=en for help in English.";
|
|
252
|
+
const HINT_WHEN_ACTIVE_IS_EN = "AIT_LANG=ko를 설정하면 한국어로 볼 수 있어요.";
|
|
253
|
+
/** The dim cross-locale discovery hint appended to top-level help output. */
|
|
254
|
+
function helpHint() {
|
|
255
|
+
return locale === "ko" ? HINT_WHEN_ACTIVE_IS_KO : HINT_WHEN_ACTIVE_IS_EN;
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
17
258
|
//#region src/utils/getPackageRoot.ts
|
|
18
259
|
function getPackageRoot(base) {
|
|
19
260
|
let dirPath = base;
|
|
@@ -154,8 +395,9 @@ var BuildCommand = class extends Command {
|
|
|
154
395
|
static paths = [["build"]];
|
|
155
396
|
static usage = Command.Usage({
|
|
156
397
|
category: "Build",
|
|
157
|
-
description: "
|
|
158
|
-
|
|
398
|
+
description: t("build.description"),
|
|
399
|
+
details: t("build.details"),
|
|
400
|
+
examples: [[t("build.example.artifact.label"), "ait build"]]
|
|
159
401
|
});
|
|
160
402
|
async execute() {
|
|
161
403
|
const buildResult = await buildArtifact();
|
|
@@ -165,14 +407,13 @@ var BuildCommand = class extends Command {
|
|
|
165
407
|
};
|
|
166
408
|
//#endregion
|
|
167
409
|
//#region src/utils/colors.ts
|
|
168
|
-
function wrap(open, close) {
|
|
169
|
-
return (input) => `\x1b[${open}m${input}\x1b[${close}m`;
|
|
170
|
-
}
|
|
171
410
|
const colors = {
|
|
172
|
-
cyan:
|
|
173
|
-
|
|
174
|
-
|
|
411
|
+
cyan: pc.cyan,
|
|
412
|
+
dim: pc.dim,
|
|
413
|
+
green: pc.green,
|
|
414
|
+
underline: pc.underline
|
|
175
415
|
};
|
|
416
|
+
const isColorSupported = pc.isColorSupported;
|
|
176
417
|
//#endregion
|
|
177
418
|
//#region src/utils/TokenStorage.ts
|
|
178
419
|
var TokenStorage = class TokenStorage {
|
|
@@ -343,42 +584,43 @@ var DeployCommand = class extends Command {
|
|
|
343
584
|
static paths = [["deploy"]];
|
|
344
585
|
static usage = Command.Usage({
|
|
345
586
|
category: "Deploy",
|
|
346
|
-
description: "
|
|
587
|
+
description: t("deploy.description"),
|
|
588
|
+
details: t("deploy.details"),
|
|
347
589
|
examples: [
|
|
348
|
-
["
|
|
349
|
-
["
|
|
350
|
-
["
|
|
351
|
-
["
|
|
352
|
-
["
|
|
590
|
+
[t("deploy.example.default.label"), "ait deploy"],
|
|
591
|
+
[t("deploy.example.location.label"), "ait deploy --location ./dist/my-app.ait"],
|
|
592
|
+
[t("deploy.example.profile.label"), "ait deploy --profile dev"],
|
|
593
|
+
[t("deploy.example.memo.label"), t("deploy.example.memo.cmd")],
|
|
594
|
+
[t("deploy.example.schemeOnly.label"), "ait deploy --scheme-only"]
|
|
353
595
|
]
|
|
354
596
|
});
|
|
355
597
|
apiKey = Option.String("--api-key", {
|
|
356
598
|
required: false,
|
|
357
|
-
description: "
|
|
599
|
+
description: t("deploy.option.apiKey")
|
|
358
600
|
});
|
|
359
601
|
workspace = Option.String("--workspace", {
|
|
360
602
|
required: false,
|
|
361
|
-
description: "
|
|
603
|
+
description: t("deploy.option.workspace")
|
|
362
604
|
});
|
|
363
605
|
profile = Option.String("--profile", {
|
|
364
606
|
required: false,
|
|
365
|
-
description: "
|
|
607
|
+
description: t("deploy.option.profile")
|
|
366
608
|
});
|
|
367
609
|
baseUrl = Option.String("--base-url", {
|
|
368
|
-
description: "
|
|
610
|
+
description: t("deploy.option.baseUrl"),
|
|
369
611
|
required: false
|
|
370
612
|
});
|
|
371
613
|
location = Option.String("--location", {
|
|
372
|
-
description: "
|
|
614
|
+
description: t("deploy.option.location"),
|
|
373
615
|
required: false
|
|
374
616
|
});
|
|
375
617
|
schemeOnly = Option.Boolean("--scheme-only", {
|
|
376
618
|
required: false,
|
|
377
|
-
description: "
|
|
619
|
+
description: t("deploy.option.schemeOnly")
|
|
378
620
|
});
|
|
379
621
|
memo = Option.String("-m,--memo", {
|
|
380
622
|
required: false,
|
|
381
|
-
description: "
|
|
623
|
+
description: t("deploy.option.memo")
|
|
382
624
|
});
|
|
383
625
|
async execute() {
|
|
384
626
|
const baseUrl = this.baseUrl;
|
|
@@ -431,9 +673,12 @@ var DeployCommand = class extends Command {
|
|
|
431
673
|
return colors.underline(color(message));
|
|
432
674
|
}
|
|
433
675
|
printResult(appName, deploymentId) {
|
|
434
|
-
const result = `intoss-private://${appName}?_deploymentId=${deploymentId}`;
|
|
676
|
+
const result = `intoss-private://${appName}?_deploymentId=${deploymentId}&host=appsInTossHost`;
|
|
435
677
|
if (this.schemeOnly) this.context.stdout.write(`${result}\n`);
|
|
436
|
-
else
|
|
678
|
+
else {
|
|
679
|
+
p.log.message(this.decorate(result, colors.green));
|
|
680
|
+
p.log.message("");
|
|
681
|
+
}
|
|
437
682
|
}
|
|
438
683
|
printError(error) {
|
|
439
684
|
if (this.schemeOnly) this.context.stdout.write(`${error.message}\n`);
|
|
@@ -785,12 +1030,13 @@ var InitCommand = class extends Command {
|
|
|
785
1030
|
static paths = [["init"]];
|
|
786
1031
|
static usage = Command.Usage({
|
|
787
1032
|
category: "Setup",
|
|
788
|
-
description: "
|
|
789
|
-
|
|
1033
|
+
description: t("init.description"),
|
|
1034
|
+
details: t("init.details"),
|
|
1035
|
+
examples: [[t("init.example.interactive.label"), "ait init"], [t("init.example.appName.label"), "ait init --app-name my-miniapp"]]
|
|
790
1036
|
});
|
|
791
1037
|
appName = Option.String("--app-name", {
|
|
792
1038
|
required: false,
|
|
793
|
-
description: "
|
|
1039
|
+
description: t("init.option.appName")
|
|
794
1040
|
});
|
|
795
1041
|
skipInput = Option.Boolean("--skip-input", {
|
|
796
1042
|
required: false,
|
|
@@ -1129,8 +1375,9 @@ var MigrationCommand = class extends Command {
|
|
|
1129
1375
|
static paths = [["migrate"]];
|
|
1130
1376
|
static usage = Command.Usage({
|
|
1131
1377
|
category: "Migration",
|
|
1132
|
-
description: "
|
|
1133
|
-
|
|
1378
|
+
description: t("migration.description"),
|
|
1379
|
+
details: t("migration.details"),
|
|
1380
|
+
examples: [[t("migration.example.v3.label"), "ait migrate v3"]]
|
|
1134
1381
|
});
|
|
1135
1382
|
target = Option.String({ required: true });
|
|
1136
1383
|
async execute() {
|
|
@@ -1143,25 +1390,96 @@ var MigrationCommand = class extends Command {
|
|
|
1143
1390
|
}
|
|
1144
1391
|
};
|
|
1145
1392
|
//#endregion
|
|
1393
|
+
//#region src/utils/groupHelp.ts
|
|
1394
|
+
/**
|
|
1395
|
+
* Renders a styled "group help" page for a Command whose only job is to
|
|
1396
|
+
* gather related subcommands under a shared path prefix (e.g. `ait token`
|
|
1397
|
+
* grouping `token add`/`token remove`).
|
|
1398
|
+
*
|
|
1399
|
+
* Clipanion has no built-in notion of a "subcommand group": a bare group
|
|
1400
|
+
* invocation just runs whatever `execute()` the group's own Command defines,
|
|
1401
|
+
* and `<group> --help` normally resolves to clipanion's *ambiguous
|
|
1402
|
+
* multi-match* help path (every registered command whose path starts with
|
|
1403
|
+
* the given prefix) rather than anything group-shaped. This helper is meant
|
|
1404
|
+
* to be called from that group Command's own `execute()` instead.
|
|
1405
|
+
*
|
|
1406
|
+
* It leans on clipanion's own rendering primitives — `cli.usage()` for each
|
|
1407
|
+
* subcommand's usage line, `formatMarkdownish` for description text, and
|
|
1408
|
+
* `cli.format()` for header/bold styling — so the output stays visually
|
|
1409
|
+
* consistent with clipanion's native `--help` pages instead of hand-rolling
|
|
1410
|
+
* ANSI output. `cli.format()`/`cli.usage()` already resolve color support
|
|
1411
|
+
* the same way the rest of the CLI's help output does, so callers don't need
|
|
1412
|
+
* to thread a `colored` flag through.
|
|
1413
|
+
*
|
|
1414
|
+
* The result always ends with the dim cross-locale discovery hint (see
|
|
1415
|
+
* `helpHint()`), so every caller of this helper — the group's own bare
|
|
1416
|
+
* invocation (e.g. `ait token`) and the `help` router (`ait help token` /
|
|
1417
|
+
* `ait token --help`, rewritten to the same thing by `rewriteHelpArgv`) —
|
|
1418
|
+
* automatically renders it without having to append it themselves.
|
|
1419
|
+
*/
|
|
1420
|
+
function renderGroupHelp(cli, groupUsage, subcommands, labels) {
|
|
1421
|
+
const format = cli.format();
|
|
1422
|
+
let result = ``;
|
|
1423
|
+
if (groupUsage.description) {
|
|
1424
|
+
result += formatMarkdownish(groupUsage.description, {
|
|
1425
|
+
format,
|
|
1426
|
+
paragraphs: false
|
|
1427
|
+
}).replace(/^./, (char) => char.toUpperCase());
|
|
1428
|
+
result += `\n`;
|
|
1429
|
+
}
|
|
1430
|
+
result += `\n`;
|
|
1431
|
+
result += `${format.header(labels.commandsHeader)}\n`;
|
|
1432
|
+
for (const subcommand of subcommands) {
|
|
1433
|
+
result += `\n`;
|
|
1434
|
+
result += cli.usage(subcommand);
|
|
1435
|
+
const description = subcommand.usage?.description;
|
|
1436
|
+
if (description) result += ` ${formatMarkdownish(description, {
|
|
1437
|
+
format,
|
|
1438
|
+
paragraphs: false
|
|
1439
|
+
})}`;
|
|
1440
|
+
}
|
|
1441
|
+
result += `\n`;
|
|
1442
|
+
result += formatMarkdownish(labels.detailHint, {
|
|
1443
|
+
format,
|
|
1444
|
+
paragraphs: true
|
|
1445
|
+
});
|
|
1446
|
+
result += `${colors.dim(helpHint())}\n`;
|
|
1447
|
+
return result;
|
|
1448
|
+
}
|
|
1449
|
+
//#endregion
|
|
1146
1450
|
//#region src/commands/token/index.ts
|
|
1451
|
+
const tokenUsage = Command.Usage({
|
|
1452
|
+
category: "Auth",
|
|
1453
|
+
description: t("token.description"),
|
|
1454
|
+
details: t("token.details"),
|
|
1455
|
+
examples: [[t("token.example.add.label"), "ait token add <profile?>"], [t("token.example.remove.label"), "ait token remove <profile?>"]]
|
|
1456
|
+
});
|
|
1147
1457
|
var TokenCommand = class extends Command {
|
|
1148
1458
|
static paths = [["token"]];
|
|
1149
|
-
static usage =
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1459
|
+
static usage = tokenUsage;
|
|
1460
|
+
async execute() {
|
|
1461
|
+
this.context.stdout.write(renderGroupHelp(this.cli, tokenUsage, [TokenAddCommand, TokenRemoveCommand], {
|
|
1462
|
+
commandsHeader: t("groupHelp.commandsHeader"),
|
|
1463
|
+
detailHint: t("groupHelp.detailHint")
|
|
1464
|
+
}));
|
|
1465
|
+
}
|
|
1155
1466
|
};
|
|
1156
1467
|
var TokenAddCommand = class extends Command {
|
|
1157
1468
|
static paths = [["token", "add"]];
|
|
1158
1469
|
static usage = Command.Usage({
|
|
1159
1470
|
category: "Auth",
|
|
1160
|
-
description: "
|
|
1161
|
-
examples: [
|
|
1471
|
+
description: t("tokenAdd.description"),
|
|
1472
|
+
examples: [
|
|
1473
|
+
[t("tokenAdd.example.default.label"), "ait token add"],
|
|
1474
|
+
[t("tokenAdd.example.profile.label"), "ait token add dev"],
|
|
1475
|
+
[t("tokenAdd.example.apiKey.label"), "ait token add dev --api-key <API_KEY>"]
|
|
1476
|
+
]
|
|
1162
1477
|
});
|
|
1163
1478
|
profile = Option.String({ required: false });
|
|
1164
|
-
apiKey = Option.String("--api-key", {
|
|
1479
|
+
apiKey = Option.String("--api-key", {
|
|
1480
|
+
required: false,
|
|
1481
|
+
description: t("tokenAdd.option.apiKey")
|
|
1482
|
+
});
|
|
1165
1483
|
async execute() {
|
|
1166
1484
|
const profile = this.profile || "default";
|
|
1167
1485
|
const secret = this.apiKey ? this.apiKey : await p.password({
|
|
@@ -1179,8 +1497,8 @@ var TokenRemoveCommand = class extends Command {
|
|
|
1179
1497
|
static paths = [["token", "remove"]];
|
|
1180
1498
|
static usage = Command.Usage({
|
|
1181
1499
|
category: "Auth",
|
|
1182
|
-
description: "
|
|
1183
|
-
examples: [["
|
|
1500
|
+
description: t("tokenRemove.description"),
|
|
1501
|
+
examples: [[t("tokenRemove.example.default.label"), "ait token remove"], [t("tokenRemove.example.profile.label"), "ait token remove dev"]]
|
|
1184
1502
|
});
|
|
1185
1503
|
profile = Option.String({ required: false });
|
|
1186
1504
|
async execute() {
|
|
@@ -1190,11 +1508,129 @@ var TokenRemoveCommand = class extends Command {
|
|
|
1190
1508
|
}
|
|
1191
1509
|
};
|
|
1192
1510
|
//#endregion
|
|
1511
|
+
//#region src/utils/argv.ts
|
|
1512
|
+
/**
|
|
1513
|
+
* Rewrites any invocation that carries a bare `--help`/`-h` token into
|
|
1514
|
+
* `["help", ...path]`, where `path` is the leading run of non-flag tokens
|
|
1515
|
+
* that precede it (stopping at the first token that looks like a flag,
|
|
1516
|
+
* i.e. starts with `-`).
|
|
1517
|
+
*
|
|
1518
|
+
* This lets a single registered command (`MainHelpCommand`, see
|
|
1519
|
+
* `src/index.ts`) own every help surface instead of relying on clipanion's
|
|
1520
|
+
* per-path internal help machinery, which:
|
|
1521
|
+
*
|
|
1522
|
+
* - never instantiates a Command for `<command> --help` (it prints usage
|
|
1523
|
+
* directly from inside `Cli#run`, see `Command#help` in clipanion's
|
|
1524
|
+
* `advanced/Command.d.ts`), so that path can't be hooked to append the
|
|
1525
|
+
* cross-locale hint line (see `helpHint()` in `../i18n`).
|
|
1526
|
+
* - resolves `<group> --help` (e.g. `ait token --help`) through its
|
|
1527
|
+
* *ambiguous multi-match* help path whenever the prefix matches more
|
|
1528
|
+
* than one registered command (`token`, `token add`, `token remove`),
|
|
1529
|
+
* printing a generic "Multiple commands match your selection" listing
|
|
1530
|
+
* instead of anything specific to the group.
|
|
1531
|
+
*
|
|
1532
|
+
* Examples:
|
|
1533
|
+
* `["deploy", "--help"]` → `["help", "deploy"]`
|
|
1534
|
+
* `["token", "add", "-h"]` → `["help", "token", "add"]`
|
|
1535
|
+
* `["--help"]` → `["help"]`
|
|
1536
|
+
* `["token", "--help"]` → `["help", "token"]`
|
|
1537
|
+
* `["deploy", "--profile", "x", "--help"]` → `["help", "deploy"]`
|
|
1538
|
+
* (the path stops at the first flag-like token, `--profile`, even
|
|
1539
|
+
* though that's not the `--help` token itself — everything from there
|
|
1540
|
+
* on is treated as options for whatever command the path names)
|
|
1541
|
+
*
|
|
1542
|
+
* `-h=<index>` (an explicit index into clipanion's ambiguous-match listing
|
|
1543
|
+
* — e.g. `ait token -h=1`) is intentionally left untouched: it never
|
|
1544
|
+
* matches the exact-token check below (it's the single string `"-h=1"`,
|
|
1545
|
+
* not `"-h"`), so it keeps flowing through clipanion's original internal
|
|
1546
|
+
* help machinery unchanged.
|
|
1547
|
+
*
|
|
1548
|
+
* Argv with no `--help`/`-h` token at all (the common case — an actual
|
|
1549
|
+
* command invocation) is returned as a shallow copy, untouched.
|
|
1550
|
+
*/
|
|
1551
|
+
function rewriteHelpArgv(argv) {
|
|
1552
|
+
if (!argv.some((token) => token === `--help` || token === `-h`)) return [...argv];
|
|
1553
|
+
const path = [];
|
|
1554
|
+
for (const token of argv) {
|
|
1555
|
+
if (token.startsWith(`-`)) break;
|
|
1556
|
+
path.push(token);
|
|
1557
|
+
}
|
|
1558
|
+
return [`help`, ...path];
|
|
1559
|
+
}
|
|
1560
|
+
//#endregion
|
|
1193
1561
|
//#region src/index.ts
|
|
1562
|
+
const HELP_ROUTES = /* @__PURE__ */ new Map([
|
|
1563
|
+
[`build`, {
|
|
1564
|
+
kind: `leaf`,
|
|
1565
|
+
commandClass: BuildCommand
|
|
1566
|
+
}],
|
|
1567
|
+
[`deploy`, {
|
|
1568
|
+
kind: `leaf`,
|
|
1569
|
+
commandClass: DeployCommand
|
|
1570
|
+
}],
|
|
1571
|
+
[`init`, {
|
|
1572
|
+
kind: `leaf`,
|
|
1573
|
+
commandClass: InitCommand
|
|
1574
|
+
}],
|
|
1575
|
+
[`migrate`, {
|
|
1576
|
+
kind: `leaf`,
|
|
1577
|
+
commandClass: MigrationCommand
|
|
1578
|
+
}],
|
|
1579
|
+
[`token`, {
|
|
1580
|
+
kind: `group`,
|
|
1581
|
+
commandClass: TokenCommand
|
|
1582
|
+
}],
|
|
1583
|
+
[`token add`, {
|
|
1584
|
+
kind: `leaf`,
|
|
1585
|
+
commandClass: TokenAddCommand
|
|
1586
|
+
}],
|
|
1587
|
+
[`token remove`, {
|
|
1588
|
+
kind: `leaf`,
|
|
1589
|
+
commandClass: TokenRemoveCommand
|
|
1590
|
+
}]
|
|
1591
|
+
]);
|
|
1592
|
+
var MainHelpCommand = class extends Command {
|
|
1593
|
+
static paths = [
|
|
1594
|
+
[`-h`],
|
|
1595
|
+
[`--help`],
|
|
1596
|
+
[`help`]
|
|
1597
|
+
];
|
|
1598
|
+
rest = Option.Rest();
|
|
1599
|
+
async execute() {
|
|
1600
|
+
if (this.rest.length === 0) {
|
|
1601
|
+
this.context.stdout.write(this.cli.usage());
|
|
1602
|
+
this.context.stdout.write(`${colors.dim(helpHint())}\n`);
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
const route = HELP_ROUTES.get(this.rest.join(` `));
|
|
1606
|
+
if (route === void 0) {
|
|
1607
|
+
this.writeUnknownPath(this.rest);
|
|
1608
|
+
return 1;
|
|
1609
|
+
}
|
|
1610
|
+
if (route.kind === `group`) {
|
|
1611
|
+
const command = new route.commandClass();
|
|
1612
|
+
command.context = this.context;
|
|
1613
|
+
command.cli = this.cli;
|
|
1614
|
+
await command.execute();
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
this.context.stdout.write(this.cli.usage(route.commandClass, { detailed: true }));
|
|
1618
|
+
this.context.stdout.write(`${colors.dim(helpHint())}\n`);
|
|
1619
|
+
}
|
|
1620
|
+
writeUnknownPath(path) {
|
|
1621
|
+
const format = this.cli.format();
|
|
1622
|
+
const label = path.join(` `);
|
|
1623
|
+
this.context.stdout.write(`${format.error(`Error`)}: ${t(`help.unknownPath`, { path: label })} ${t(`help.unknownPath.guide`)}\n\n`);
|
|
1624
|
+
this.context.stdout.write(this.cli.usage());
|
|
1625
|
+
this.context.stdout.write(`${colors.dim(helpHint())}\n`);
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1194
1628
|
const cli = new Cli({
|
|
1195
|
-
binaryLabel: "
|
|
1196
|
-
binaryName: "
|
|
1197
|
-
|
|
1629
|
+
binaryLabel: "Apps in Toss CLI",
|
|
1630
|
+
binaryName: "ait",
|
|
1631
|
+
binaryVersion: "3.1.0",
|
|
1632
|
+
enableCapture: true,
|
|
1633
|
+
enableColors: isColorSupported
|
|
1198
1634
|
});
|
|
1199
1635
|
cli.register(BuildCommand);
|
|
1200
1636
|
cli.register(DeployCommand);
|
|
@@ -1203,7 +1639,10 @@ cli.register(MigrationCommand);
|
|
|
1203
1639
|
cli.register(TokenCommand);
|
|
1204
1640
|
cli.register(TokenAddCommand);
|
|
1205
1641
|
cli.register(TokenRemoveCommand);
|
|
1206
|
-
cli.register(
|
|
1207
|
-
cli.
|
|
1642
|
+
cli.register(MainHelpCommand);
|
|
1643
|
+
cli.register(Builtins.VersionCommand);
|
|
1644
|
+
const rawArgv = process.argv.slice(2);
|
|
1645
|
+
const argv = rawArgv.length === 0 ? ["--help"] : rewriteHelpArgv(rawArgv);
|
|
1646
|
+
cli.runExit(argv);
|
|
1208
1647
|
//#endregion
|
|
1209
1648
|
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apps-in-toss/cli",
|
|
3
|
-
"version": "3.1.0
|
|
4
|
-
"license": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ait": "dist/index.js"
|
|
7
7
|
},
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"c12": "3.3.4",
|
|
24
24
|
"clipanion": "^4.0.0-rc.4",
|
|
25
25
|
"jscodeshift": "^17.1.2",
|
|
26
|
+
"picocolors": "^1.1.1",
|
|
26
27
|
"uuidv7": "^1.2.1"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
@@ -31,4 +32,4 @@
|
|
|
31
32
|
"tsdown": "0.22.7",
|
|
32
33
|
"typescript": "5.9.3"
|
|
33
34
|
}
|
|
34
|
-
}
|
|
35
|
+
}
|