@apps-in-toss/cli 3.0.3 → 3.0.4
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/LICENSE +201 -28
- package/dist/index.js +504 -47
- package/package.json +3 -2
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,19 +1,260 @@
|
|
|
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
|
-
import { appendFile, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { appendFile, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
import path$1, { basename, dirname, extname, join, parse, resolve } from "node:path";
|
|
8
8
|
import { AppsInTossBundle, PlatformType } from "@apps-in-toss/ait-format";
|
|
9
9
|
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;
|
|
@@ -49,7 +290,12 @@ async function buildArtifact() {
|
|
|
49
290
|
}
|
|
50
291
|
});
|
|
51
292
|
appsInTossConfig.permissions.forEach(({ name, access }) => writer.addPermission(name, access));
|
|
52
|
-
const
|
|
293
|
+
const webBundleDir = path$1.resolve(projectRoot, appsInTossConfig.webBundleDir);
|
|
294
|
+
if ((await stat(webBundleDir).catch(() => null))?.isDirectory() !== true) {
|
|
295
|
+
p.log.error(`웹 빌드 디렉토리(${appsInTossConfig.webBundleDir})가 존재하지 않습니다. 웹 빌드를 먼저 실행해주세요.`);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
const webBundleFiles = await getWebBundleFiles(webBundleDir);
|
|
53
299
|
if (webBundleFiles.find((file) => file.name === "index.html") == null) {
|
|
54
300
|
p.log.error("웹 빌드 디렉토리에 index.html이 존재해야 합니다.");
|
|
55
301
|
process.exit(1);
|
|
@@ -95,7 +341,23 @@ async function getAppsInTossConfig() {
|
|
|
95
341
|
packageJson: false
|
|
96
342
|
});
|
|
97
343
|
if (configFile == null) throw new Error("Config 파일을 찾을 수 없습니다.");
|
|
98
|
-
|
|
344
|
+
if (typeof config.appName !== "string" || config.appName.trim() === "") {
|
|
345
|
+
p.log.error("apps-in-toss.config에 appName이 설정되어야 합니다. (예: appName: 'my-app')");
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
if (typeof config.brand?.primaryColor !== "string" || config.brand.primaryColor.trim() === "") {
|
|
349
|
+
p.log.error("apps-in-toss.config에 brand.primaryColor가 설정되어야 합니다. (예: brand: { primaryColor: '#3182F6' })");
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
const webBundleDir = config.webBundleDir ?? "dist";
|
|
353
|
+
if (typeof webBundleDir !== "string" || webBundleDir.trim() === "") {
|
|
354
|
+
p.log.error("apps-in-toss.config의 webBundleDir이 올바르지 않습니다.");
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
...config,
|
|
359
|
+
webBundleDir
|
|
360
|
+
};
|
|
99
361
|
}
|
|
100
362
|
function getUnityMetadata() {
|
|
101
363
|
const unityMetadataString = process.env.UNITY_METADATA;
|
|
@@ -133,8 +395,9 @@ var BuildCommand = class extends Command {
|
|
|
133
395
|
static paths = [["build"]];
|
|
134
396
|
static usage = Command.Usage({
|
|
135
397
|
category: "Build",
|
|
136
|
-
description: "
|
|
137
|
-
|
|
398
|
+
description: t("build.description"),
|
|
399
|
+
details: t("build.details"),
|
|
400
|
+
examples: [[t("build.example.artifact.label"), "ait build"]]
|
|
138
401
|
});
|
|
139
402
|
async execute() {
|
|
140
403
|
const buildResult = await buildArtifact();
|
|
@@ -144,14 +407,13 @@ var BuildCommand = class extends Command {
|
|
|
144
407
|
};
|
|
145
408
|
//#endregion
|
|
146
409
|
//#region src/utils/colors.ts
|
|
147
|
-
function wrap(open, close) {
|
|
148
|
-
return (input) => `\x1b[${open}m${input}\x1b[${close}m`;
|
|
149
|
-
}
|
|
150
410
|
const colors = {
|
|
151
|
-
cyan:
|
|
152
|
-
|
|
153
|
-
|
|
411
|
+
cyan: pc.cyan,
|
|
412
|
+
dim: pc.dim,
|
|
413
|
+
green: pc.green,
|
|
414
|
+
underline: pc.underline
|
|
154
415
|
};
|
|
416
|
+
const isColorSupported = pc.isColorSupported;
|
|
155
417
|
//#endregion
|
|
156
418
|
//#region src/utils/TokenStorage.ts
|
|
157
419
|
var TokenStorage = class TokenStorage {
|
|
@@ -322,42 +584,43 @@ var DeployCommand = class extends Command {
|
|
|
322
584
|
static paths = [["deploy"]];
|
|
323
585
|
static usage = Command.Usage({
|
|
324
586
|
category: "Deploy",
|
|
325
|
-
description: "
|
|
587
|
+
description: t("deploy.description"),
|
|
588
|
+
details: t("deploy.details"),
|
|
326
589
|
examples: [
|
|
327
|
-
["
|
|
328
|
-
["
|
|
329
|
-
["
|
|
330
|
-
["
|
|
331
|
-
["
|
|
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"]
|
|
332
595
|
]
|
|
333
596
|
});
|
|
334
597
|
apiKey = Option.String("--api-key", {
|
|
335
598
|
required: false,
|
|
336
|
-
description: "
|
|
599
|
+
description: t("deploy.option.apiKey")
|
|
337
600
|
});
|
|
338
601
|
workspace = Option.String("--workspace", {
|
|
339
602
|
required: false,
|
|
340
|
-
description: "
|
|
603
|
+
description: t("deploy.option.workspace")
|
|
341
604
|
});
|
|
342
605
|
profile = Option.String("--profile", {
|
|
343
606
|
required: false,
|
|
344
|
-
description: "
|
|
607
|
+
description: t("deploy.option.profile")
|
|
345
608
|
});
|
|
346
609
|
baseUrl = Option.String("--base-url", {
|
|
347
|
-
description: "
|
|
610
|
+
description: t("deploy.option.baseUrl"),
|
|
348
611
|
required: false
|
|
349
612
|
});
|
|
350
613
|
location = Option.String("--location", {
|
|
351
|
-
description: "
|
|
614
|
+
description: t("deploy.option.location"),
|
|
352
615
|
required: false
|
|
353
616
|
});
|
|
354
617
|
schemeOnly = Option.Boolean("--scheme-only", {
|
|
355
618
|
required: false,
|
|
356
|
-
description: "
|
|
619
|
+
description: t("deploy.option.schemeOnly")
|
|
357
620
|
});
|
|
358
621
|
memo = Option.String("-m,--memo", {
|
|
359
622
|
required: false,
|
|
360
|
-
description: "
|
|
623
|
+
description: t("deploy.option.memo")
|
|
361
624
|
});
|
|
362
625
|
async execute() {
|
|
363
626
|
const baseUrl = this.baseUrl;
|
|
@@ -410,7 +673,7 @@ var DeployCommand = class extends Command {
|
|
|
410
673
|
return colors.underline(color(message));
|
|
411
674
|
}
|
|
412
675
|
printResult(appName, deploymentId) {
|
|
413
|
-
const result = `intoss-private://${appName}?_deploymentId=${deploymentId}`;
|
|
676
|
+
const result = `intoss-private://${appName}?_deploymentId=${deploymentId}&host=appsInTossHost`;
|
|
414
677
|
if (this.schemeOnly) this.context.stdout.write(`${result}\n`);
|
|
415
678
|
else p.note(this.decorate(result, colors.green));
|
|
416
679
|
}
|
|
@@ -764,12 +1027,13 @@ var InitCommand = class extends Command {
|
|
|
764
1027
|
static paths = [["init"]];
|
|
765
1028
|
static usage = Command.Usage({
|
|
766
1029
|
category: "Setup",
|
|
767
|
-
description: "
|
|
768
|
-
|
|
1030
|
+
description: t("init.description"),
|
|
1031
|
+
details: t("init.details"),
|
|
1032
|
+
examples: [[t("init.example.interactive.label"), "ait init"], [t("init.example.appName.label"), "ait init --app-name my-miniapp"]]
|
|
769
1033
|
});
|
|
770
1034
|
appName = Option.String("--app-name", {
|
|
771
1035
|
required: false,
|
|
772
|
-
description: "
|
|
1036
|
+
description: t("init.option.appName")
|
|
773
1037
|
});
|
|
774
1038
|
skipInput = Option.Boolean("--skip-input", {
|
|
775
1039
|
required: false,
|
|
@@ -1108,8 +1372,9 @@ var MigrationCommand = class extends Command {
|
|
|
1108
1372
|
static paths = [["migrate"]];
|
|
1109
1373
|
static usage = Command.Usage({
|
|
1110
1374
|
category: "Migration",
|
|
1111
|
-
description: "
|
|
1112
|
-
|
|
1375
|
+
description: t("migration.description"),
|
|
1376
|
+
details: t("migration.details"),
|
|
1377
|
+
examples: [[t("migration.example.v3.label"), "ait migrate v3"]]
|
|
1113
1378
|
});
|
|
1114
1379
|
target = Option.String({ required: true });
|
|
1115
1380
|
async execute() {
|
|
@@ -1122,25 +1387,96 @@ var MigrationCommand = class extends Command {
|
|
|
1122
1387
|
}
|
|
1123
1388
|
};
|
|
1124
1389
|
//#endregion
|
|
1390
|
+
//#region src/utils/groupHelp.ts
|
|
1391
|
+
/**
|
|
1392
|
+
* Renders a styled "group help" page for a Command whose only job is to
|
|
1393
|
+
* gather related subcommands under a shared path prefix (e.g. `ait token`
|
|
1394
|
+
* grouping `token add`/`token remove`).
|
|
1395
|
+
*
|
|
1396
|
+
* Clipanion has no built-in notion of a "subcommand group": a bare group
|
|
1397
|
+
* invocation just runs whatever `execute()` the group's own Command defines,
|
|
1398
|
+
* and `<group> --help` normally resolves to clipanion's *ambiguous
|
|
1399
|
+
* multi-match* help path (every registered command whose path starts with
|
|
1400
|
+
* the given prefix) rather than anything group-shaped. This helper is meant
|
|
1401
|
+
* to be called from that group Command's own `execute()` instead.
|
|
1402
|
+
*
|
|
1403
|
+
* It leans on clipanion's own rendering primitives — `cli.usage()` for each
|
|
1404
|
+
* subcommand's usage line, `formatMarkdownish` for description text, and
|
|
1405
|
+
* `cli.format()` for header/bold styling — so the output stays visually
|
|
1406
|
+
* consistent with clipanion's native `--help` pages instead of hand-rolling
|
|
1407
|
+
* ANSI output. `cli.format()`/`cli.usage()` already resolve color support
|
|
1408
|
+
* the same way the rest of the CLI's help output does, so callers don't need
|
|
1409
|
+
* to thread a `colored` flag through.
|
|
1410
|
+
*
|
|
1411
|
+
* The result always ends with the dim cross-locale discovery hint (see
|
|
1412
|
+
* `helpHint()`), so every caller of this helper — the group's own bare
|
|
1413
|
+
* invocation (e.g. `ait token`) and the `help` router (`ait help token` /
|
|
1414
|
+
* `ait token --help`, rewritten to the same thing by `rewriteHelpArgv`) —
|
|
1415
|
+
* automatically renders it without having to append it themselves.
|
|
1416
|
+
*/
|
|
1417
|
+
function renderGroupHelp(cli, groupUsage, subcommands, labels) {
|
|
1418
|
+
const format = cli.format();
|
|
1419
|
+
let result = ``;
|
|
1420
|
+
if (groupUsage.description) {
|
|
1421
|
+
result += formatMarkdownish(groupUsage.description, {
|
|
1422
|
+
format,
|
|
1423
|
+
paragraphs: false
|
|
1424
|
+
}).replace(/^./, (char) => char.toUpperCase());
|
|
1425
|
+
result += `\n`;
|
|
1426
|
+
}
|
|
1427
|
+
result += `\n`;
|
|
1428
|
+
result += `${format.header(labels.commandsHeader)}\n`;
|
|
1429
|
+
for (const subcommand of subcommands) {
|
|
1430
|
+
result += `\n`;
|
|
1431
|
+
result += cli.usage(subcommand);
|
|
1432
|
+
const description = subcommand.usage?.description;
|
|
1433
|
+
if (description) result += ` ${formatMarkdownish(description, {
|
|
1434
|
+
format,
|
|
1435
|
+
paragraphs: false
|
|
1436
|
+
})}`;
|
|
1437
|
+
}
|
|
1438
|
+
result += `\n`;
|
|
1439
|
+
result += formatMarkdownish(labels.detailHint, {
|
|
1440
|
+
format,
|
|
1441
|
+
paragraphs: true
|
|
1442
|
+
});
|
|
1443
|
+
result += `${colors.dim(helpHint())}\n`;
|
|
1444
|
+
return result;
|
|
1445
|
+
}
|
|
1446
|
+
//#endregion
|
|
1125
1447
|
//#region src/commands/token/index.ts
|
|
1448
|
+
const tokenUsage = Command.Usage({
|
|
1449
|
+
category: "Auth",
|
|
1450
|
+
description: t("token.description"),
|
|
1451
|
+
details: t("token.details"),
|
|
1452
|
+
examples: [[t("token.example.add.label"), "ait token add <profile?>"], [t("token.example.remove.label"), "ait token remove <profile?>"]]
|
|
1453
|
+
});
|
|
1126
1454
|
var TokenCommand = class extends Command {
|
|
1127
1455
|
static paths = [["token"]];
|
|
1128
|
-
static usage =
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1456
|
+
static usage = tokenUsage;
|
|
1457
|
+
async execute() {
|
|
1458
|
+
this.context.stdout.write(renderGroupHelp(this.cli, tokenUsage, [TokenAddCommand, TokenRemoveCommand], {
|
|
1459
|
+
commandsHeader: t("groupHelp.commandsHeader"),
|
|
1460
|
+
detailHint: t("groupHelp.detailHint")
|
|
1461
|
+
}));
|
|
1462
|
+
}
|
|
1134
1463
|
};
|
|
1135
1464
|
var TokenAddCommand = class extends Command {
|
|
1136
1465
|
static paths = [["token", "add"]];
|
|
1137
1466
|
static usage = Command.Usage({
|
|
1138
1467
|
category: "Auth",
|
|
1139
|
-
description: "
|
|
1140
|
-
examples: [
|
|
1468
|
+
description: t("tokenAdd.description"),
|
|
1469
|
+
examples: [
|
|
1470
|
+
[t("tokenAdd.example.default.label"), "ait token add"],
|
|
1471
|
+
[t("tokenAdd.example.profile.label"), "ait token add dev"],
|
|
1472
|
+
[t("tokenAdd.example.apiKey.label"), "ait token add dev --api-key <API_KEY>"]
|
|
1473
|
+
]
|
|
1141
1474
|
});
|
|
1142
1475
|
profile = Option.String({ required: false });
|
|
1143
|
-
apiKey = Option.String("--api-key", {
|
|
1476
|
+
apiKey = Option.String("--api-key", {
|
|
1477
|
+
required: false,
|
|
1478
|
+
description: t("tokenAdd.option.apiKey")
|
|
1479
|
+
});
|
|
1144
1480
|
async execute() {
|
|
1145
1481
|
const profile = this.profile || "default";
|
|
1146
1482
|
const secret = this.apiKey ? this.apiKey : await p.password({
|
|
@@ -1158,8 +1494,8 @@ var TokenRemoveCommand = class extends Command {
|
|
|
1158
1494
|
static paths = [["token", "remove"]];
|
|
1159
1495
|
static usage = Command.Usage({
|
|
1160
1496
|
category: "Auth",
|
|
1161
|
-
description: "
|
|
1162
|
-
examples: [["
|
|
1497
|
+
description: t("tokenRemove.description"),
|
|
1498
|
+
examples: [[t("tokenRemove.example.default.label"), "ait token remove"], [t("tokenRemove.example.profile.label"), "ait token remove dev"]]
|
|
1163
1499
|
});
|
|
1164
1500
|
profile = Option.String({ required: false });
|
|
1165
1501
|
async execute() {
|
|
@@ -1169,11 +1505,129 @@ var TokenRemoveCommand = class extends Command {
|
|
|
1169
1505
|
}
|
|
1170
1506
|
};
|
|
1171
1507
|
//#endregion
|
|
1508
|
+
//#region src/utils/argv.ts
|
|
1509
|
+
/**
|
|
1510
|
+
* Rewrites any invocation that carries a bare `--help`/`-h` token into
|
|
1511
|
+
* `["help", ...path]`, where `path` is the leading run of non-flag tokens
|
|
1512
|
+
* that precede it (stopping at the first token that looks like a flag,
|
|
1513
|
+
* i.e. starts with `-`).
|
|
1514
|
+
*
|
|
1515
|
+
* This lets a single registered command (`MainHelpCommand`, see
|
|
1516
|
+
* `src/index.ts`) own every help surface instead of relying on clipanion's
|
|
1517
|
+
* per-path internal help machinery, which:
|
|
1518
|
+
*
|
|
1519
|
+
* - never instantiates a Command for `<command> --help` (it prints usage
|
|
1520
|
+
* directly from inside `Cli#run`, see `Command#help` in clipanion's
|
|
1521
|
+
* `advanced/Command.d.ts`), so that path can't be hooked to append the
|
|
1522
|
+
* cross-locale hint line (see `helpHint()` in `../i18n`).
|
|
1523
|
+
* - resolves `<group> --help` (e.g. `ait token --help`) through its
|
|
1524
|
+
* *ambiguous multi-match* help path whenever the prefix matches more
|
|
1525
|
+
* than one registered command (`token`, `token add`, `token remove`),
|
|
1526
|
+
* printing a generic "Multiple commands match your selection" listing
|
|
1527
|
+
* instead of anything specific to the group.
|
|
1528
|
+
*
|
|
1529
|
+
* Examples:
|
|
1530
|
+
* `["deploy", "--help"]` → `["help", "deploy"]`
|
|
1531
|
+
* `["token", "add", "-h"]` → `["help", "token", "add"]`
|
|
1532
|
+
* `["--help"]` → `["help"]`
|
|
1533
|
+
* `["token", "--help"]` → `["help", "token"]`
|
|
1534
|
+
* `["deploy", "--profile", "x", "--help"]` → `["help", "deploy"]`
|
|
1535
|
+
* (the path stops at the first flag-like token, `--profile`, even
|
|
1536
|
+
* though that's not the `--help` token itself — everything from there
|
|
1537
|
+
* on is treated as options for whatever command the path names)
|
|
1538
|
+
*
|
|
1539
|
+
* `-h=<index>` (an explicit index into clipanion's ambiguous-match listing
|
|
1540
|
+
* — e.g. `ait token -h=1`) is intentionally left untouched: it never
|
|
1541
|
+
* matches the exact-token check below (it's the single string `"-h=1"`,
|
|
1542
|
+
* not `"-h"`), so it keeps flowing through clipanion's original internal
|
|
1543
|
+
* help machinery unchanged.
|
|
1544
|
+
*
|
|
1545
|
+
* Argv with no `--help`/`-h` token at all (the common case — an actual
|
|
1546
|
+
* command invocation) is returned as a shallow copy, untouched.
|
|
1547
|
+
*/
|
|
1548
|
+
function rewriteHelpArgv(argv) {
|
|
1549
|
+
if (!argv.some((token) => token === `--help` || token === `-h`)) return [...argv];
|
|
1550
|
+
const path = [];
|
|
1551
|
+
for (const token of argv) {
|
|
1552
|
+
if (token.startsWith(`-`)) break;
|
|
1553
|
+
path.push(token);
|
|
1554
|
+
}
|
|
1555
|
+
return [`help`, ...path];
|
|
1556
|
+
}
|
|
1557
|
+
//#endregion
|
|
1172
1558
|
//#region src/index.ts
|
|
1559
|
+
const HELP_ROUTES = /* @__PURE__ */ new Map([
|
|
1560
|
+
[`build`, {
|
|
1561
|
+
kind: `leaf`,
|
|
1562
|
+
commandClass: BuildCommand
|
|
1563
|
+
}],
|
|
1564
|
+
[`deploy`, {
|
|
1565
|
+
kind: `leaf`,
|
|
1566
|
+
commandClass: DeployCommand
|
|
1567
|
+
}],
|
|
1568
|
+
[`init`, {
|
|
1569
|
+
kind: `leaf`,
|
|
1570
|
+
commandClass: InitCommand
|
|
1571
|
+
}],
|
|
1572
|
+
[`migrate`, {
|
|
1573
|
+
kind: `leaf`,
|
|
1574
|
+
commandClass: MigrationCommand
|
|
1575
|
+
}],
|
|
1576
|
+
[`token`, {
|
|
1577
|
+
kind: `group`,
|
|
1578
|
+
commandClass: TokenCommand
|
|
1579
|
+
}],
|
|
1580
|
+
[`token add`, {
|
|
1581
|
+
kind: `leaf`,
|
|
1582
|
+
commandClass: TokenAddCommand
|
|
1583
|
+
}],
|
|
1584
|
+
[`token remove`, {
|
|
1585
|
+
kind: `leaf`,
|
|
1586
|
+
commandClass: TokenRemoveCommand
|
|
1587
|
+
}]
|
|
1588
|
+
]);
|
|
1589
|
+
var MainHelpCommand = class extends Command {
|
|
1590
|
+
static paths = [
|
|
1591
|
+
[`-h`],
|
|
1592
|
+
[`--help`],
|
|
1593
|
+
[`help`]
|
|
1594
|
+
];
|
|
1595
|
+
rest = Option.Rest();
|
|
1596
|
+
async execute() {
|
|
1597
|
+
if (this.rest.length === 0) {
|
|
1598
|
+
this.context.stdout.write(this.cli.usage());
|
|
1599
|
+
this.context.stdout.write(`${colors.dim(helpHint())}\n`);
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
const route = HELP_ROUTES.get(this.rest.join(` `));
|
|
1603
|
+
if (route === void 0) {
|
|
1604
|
+
this.writeUnknownPath(this.rest);
|
|
1605
|
+
return 1;
|
|
1606
|
+
}
|
|
1607
|
+
if (route.kind === `group`) {
|
|
1608
|
+
const command = new route.commandClass();
|
|
1609
|
+
command.context = this.context;
|
|
1610
|
+
command.cli = this.cli;
|
|
1611
|
+
await command.execute();
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
this.context.stdout.write(this.cli.usage(route.commandClass, { detailed: true }));
|
|
1615
|
+
this.context.stdout.write(`${colors.dim(helpHint())}\n`);
|
|
1616
|
+
}
|
|
1617
|
+
writeUnknownPath(path) {
|
|
1618
|
+
const format = this.cli.format();
|
|
1619
|
+
const label = path.join(` `);
|
|
1620
|
+
this.context.stdout.write(`${format.error(`Error`)}: ${t(`help.unknownPath`, { path: label })} ${t(`help.unknownPath.guide`)}\n\n`);
|
|
1621
|
+
this.context.stdout.write(this.cli.usage());
|
|
1622
|
+
this.context.stdout.write(`${colors.dim(helpHint())}\n`);
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1173
1625
|
const cli = new Cli({
|
|
1174
|
-
binaryLabel: "
|
|
1175
|
-
binaryName: "
|
|
1176
|
-
|
|
1626
|
+
binaryLabel: "Apps in Toss CLI",
|
|
1627
|
+
binaryName: "ait",
|
|
1628
|
+
binaryVersion: "3.0.4",
|
|
1629
|
+
enableCapture: true,
|
|
1630
|
+
enableColors: isColorSupported
|
|
1177
1631
|
});
|
|
1178
1632
|
cli.register(BuildCommand);
|
|
1179
1633
|
cli.register(DeployCommand);
|
|
@@ -1182,7 +1636,10 @@ cli.register(MigrationCommand);
|
|
|
1182
1636
|
cli.register(TokenCommand);
|
|
1183
1637
|
cli.register(TokenAddCommand);
|
|
1184
1638
|
cli.register(TokenRemoveCommand);
|
|
1185
|
-
cli.register(
|
|
1186
|
-
cli.
|
|
1639
|
+
cli.register(MainHelpCommand);
|
|
1640
|
+
cli.register(Builtins.VersionCommand);
|
|
1641
|
+
const rawArgv = process.argv.slice(2);
|
|
1642
|
+
const argv = rawArgv.length === 0 ? ["--help"] : rewriteHelpArgv(rawArgv);
|
|
1643
|
+
cli.runExit(argv);
|
|
1187
1644
|
//#endregion
|
|
1188
1645
|
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apps-in-toss/cli",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"license": "
|
|
3
|
+
"version": "3.0.4",
|
|
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": {
|