create-jilatax 0.0.3 โ 0.0.5
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/README.md +42 -23
- package/THIRD_PARTY_NOTICES.md +7 -0
- package/dist/bin.cjs +6 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +8 -0
- package/dist/cli-B8DIbCrH.js +387 -0
- package/dist/cli-DMT97_QW.cjs +452 -0
- package/dist/index.cjs +8 -6
- package/dist/index.d.cts +32 -3
- package/dist/index.d.ts +32 -3
- package/dist/index.js +2 -6
- package/package.json +13 -5
- package/template/README.md.tmpl +34 -0
- package/template/android/app/build.gradle.kts +112 -0
- package/template/android/app/proguard-rules.pro +1 -0
- package/template/android/app/src/debug/AndroidManifest.xml +8 -0
- package/template/android/app/src/main/AndroidManifest.xml +30 -0
- package/template/android/app/src/main/res/drawable/jilatax_mark_foreground.xml +11 -0
- package/template/android/app/src/main/res/drawable/jilatax_splash.xml +9 -0
- package/template/android/app/src/main/res/mipmap-anydpi/jilatax_launcher.xml +12 -0
- package/template/android/app/src/main/res/mipmap-anydpi/jilatax_launcher_round.xml +12 -0
- package/template/android/app/src/main/res/mipmap-anydpi-v26/jilatax_launcher.xml +5 -0
- package/template/android/app/src/main/res/mipmap-anydpi-v26/jilatax_launcher_round.xml +5 -0
- package/template/android/app/src/main/res/values/styles.xml +16 -0
- package/template/android/app/src/main/res/values-v31/styles.xml +8 -0
- package/template/android/build.gradle.kts +5 -0
- package/template/android/gradle/wrapper/gradle-wrapper.jar.base64 +1112 -0
- package/template/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/template/android/gradle.properties +6 -0
- package/template/android/gradlew +248 -0
- package/template/android/gradlew.bat +92 -0
- package/template/android/keystore.properties.example +5 -0
- package/template/android/settings.gradle.kts.tmpl +29 -0
- package/template/gitignore +22 -0
- package/template/lynx.config.ts +25 -0
- package/template/src/App.css +95 -0
- package/template/src/App.tsx.tmpl +23 -0
- package/template/src/index.tsx +9 -0
- package/template/src/rspeedy-env.d.ts +1 -0
- package/template/src/tsconfig.json +13 -0
- package/template/tsconfig.json +15 -0
- package/template/tsconfig.node.json +12 -0
package/README.md
CHANGED
|
@@ -1,21 +1,55 @@
|
|
|
1
1
|
# create-jilatax
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Create an Android-first Lynx application backed by the Jilatax runtime and CLI.
|
|
4
4
|
|
|
5
|
-
Requires Node.js 22.18 or newer.
|
|
5
|
+
Requires Node.js 22.18 or newer and Bun.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Create an application
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bun create jilatax@latest
|
|
11
|
+
```
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bunx create-jilatax@latest my-app
|
|
16
|
+
cd my-app
|
|
17
|
+
bun run run:android
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The creator asks for the application name, Android package ID, and whether to
|
|
21
|
+
install dependencies. In non-interactive environments, pass the directory and
|
|
22
|
+
options explicitly:
|
|
8
23
|
|
|
9
24
|
```bash
|
|
10
|
-
|
|
25
|
+
bunx create-jilatax@latest my-app \
|
|
26
|
+
--name "My App" \
|
|
27
|
+
--package-id com.example.my_app \
|
|
28
|
+
--skip-install
|
|
11
29
|
```
|
|
12
30
|
|
|
13
|
-
|
|
31
|
+
Generated projects include:
|
|
32
|
+
|
|
33
|
+
- a Lynx and Rspeedy application;
|
|
34
|
+
- `app.json` as the Jilatax application configuration;
|
|
35
|
+
- an Android Gradle project linked to `jilatax/android`;
|
|
36
|
+
- `bun run run:android` for development on a connected device;
|
|
37
|
+
- `bun run create:aab` for a Play Store Android App Bundle.
|
|
38
|
+
|
|
39
|
+
The creator only writes the project and can run `bun install`. Device, Gradle,
|
|
40
|
+
bundle, APK, and AAB orchestration belongs to `@jilatax/cli`.
|
|
41
|
+
|
|
42
|
+
## Programmatic API
|
|
14
43
|
|
|
15
44
|
```ts
|
|
16
|
-
import {
|
|
45
|
+
import { createProject } from 'create-jilatax';
|
|
17
46
|
|
|
18
|
-
|
|
47
|
+
await createProject({
|
|
48
|
+
targetDirectory: './my-app',
|
|
49
|
+
displayName: 'My App',
|
|
50
|
+
packageId: 'com.example.my_app',
|
|
51
|
+
install: false,
|
|
52
|
+
});
|
|
19
53
|
```
|
|
20
54
|
|
|
21
55
|
## Development
|
|
@@ -25,21 +59,6 @@ bun install
|
|
|
25
59
|
bun run check
|
|
26
60
|
```
|
|
27
61
|
|
|
28
|
-
##
|
|
62
|
+
## License
|
|
29
63
|
|
|
30
64
|
MIT ยฉ [JilataX](https://jilatax.dev) โ see [`LICENSE`](./LICENSE).
|
|
31
|
-
|
|
32
|
-
<br>
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
<div align="center">
|
|
37
|
-
<p>
|
|
38
|
-
Built with <strong>Bun</strong>, <strong>TypeScript</strong>, and <strong>Lynx</strong>.
|
|
39
|
-
</p>
|
|
40
|
-
<p>
|
|
41
|
-
<a href="https://www.jilatax.dev/jilatax">Website</a> ยท
|
|
42
|
-
<a href="https://github.com/jilatax/jilatax">GitHub</a> ยท
|
|
43
|
-
<a href="https://www.npmjs.com/package/jilatax">npm</a>
|
|
44
|
-
</p>
|
|
45
|
-
</div>
|
package/dist/bin.cjs
ADDED
package/dist/bin.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import * as prompts from "@clack/prompts";
|
|
2
|
+
import { chmod, copyFile, lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { stdin, stdout } from "node:process";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { parseAppConfig, serializeAndroidProjectConfig } from "jilatax";
|
|
8
|
+
//#region src/generator.ts
|
|
9
|
+
const JILATAX_CLI_VERSION = "^0.0.8";
|
|
10
|
+
const JILATAX_VERSION = "^0.0.5";
|
|
11
|
+
const LYNX_QRCODE_PLUGIN_VERSION = "^0.4.4";
|
|
12
|
+
const LYNX_REACT_VERSION = "^0.116.2";
|
|
13
|
+
const LYNX_TYPES_VERSION = "^3.7.0";
|
|
14
|
+
const REACT_PLUGIN_VERSION = "^0.12.7";
|
|
15
|
+
const RSPEEDY_VERSION = "^0.13.3";
|
|
16
|
+
const TEXT_TEMPLATE_FILES = /* @__PURE__ */ new Set([
|
|
17
|
+
"README.md.tmpl",
|
|
18
|
+
"android/settings.gradle.kts.tmpl",
|
|
19
|
+
"src/App.tsx.tmpl"
|
|
20
|
+
]);
|
|
21
|
+
async function createProject(options) {
|
|
22
|
+
const projectDirectory = path.resolve(options.targetDirectory);
|
|
23
|
+
const projectName = normalizeProjectName(path.basename(projectDirectory));
|
|
24
|
+
const displayName = normalizeDisplayName(options.displayName ?? titleCase$1(projectName));
|
|
25
|
+
const packageId = validatePackageId(options.packageId ?? defaultPackageId(projectName));
|
|
26
|
+
const config = createInitialConfig(displayName, projectName, packageId);
|
|
27
|
+
await assertTargetDoesNotExist(projectDirectory);
|
|
28
|
+
const parentDirectory = path.dirname(projectDirectory);
|
|
29
|
+
await mkdir(parentDirectory, { recursive: true });
|
|
30
|
+
const stagingDirectory = await mkdtemp(path.join(parentDirectory, `.${projectName}-`));
|
|
31
|
+
try {
|
|
32
|
+
await renderTemplate(stagingDirectory, {
|
|
33
|
+
displayName,
|
|
34
|
+
projectName
|
|
35
|
+
});
|
|
36
|
+
await writeGeneratedMetadata(stagingDirectory, config, projectName);
|
|
37
|
+
await rename(stagingDirectory, projectDirectory);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
await rm(stagingDirectory, {
|
|
40
|
+
force: true,
|
|
41
|
+
recursive: true
|
|
42
|
+
});
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
const shouldInstall = options.install === true;
|
|
46
|
+
if (shouldInstall) await (options.installer ?? installWithBun)(projectDirectory);
|
|
47
|
+
return {
|
|
48
|
+
displayName,
|
|
49
|
+
installed: shouldInstall,
|
|
50
|
+
packageId,
|
|
51
|
+
projectDirectory,
|
|
52
|
+
projectName
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function normalizeProjectName(value) {
|
|
56
|
+
const projectName = value.trim().toLowerCase();
|
|
57
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/u.test(projectName) || projectName.length > 214) throw new Error("Project directory name must start with a letter or number and contain only lowercase letters, numbers, dots, hyphens, or underscores.");
|
|
58
|
+
return projectName;
|
|
59
|
+
}
|
|
60
|
+
function normalizeDisplayName(value) {
|
|
61
|
+
const displayName = value.trim().replace(/\s+/gu, " ");
|
|
62
|
+
if (displayName.length === 0 || displayName.length > 80) throw new Error("Application name must contain between 1 and 80 characters.");
|
|
63
|
+
return displayName;
|
|
64
|
+
}
|
|
65
|
+
function defaultPackageId(projectName) {
|
|
66
|
+
return `com.example.${projectName.replace(/[^a-z0-9_]+/gu, "_").replace(/^[^a-z]+/u, "").replace(/_+$/gu, "") || "app"}`;
|
|
67
|
+
}
|
|
68
|
+
function validatePackageId(value) {
|
|
69
|
+
const packageId = value.trim();
|
|
70
|
+
parseAppConfig({ jilatax: {
|
|
71
|
+
android: { package: packageId },
|
|
72
|
+
name: "Package validation"
|
|
73
|
+
} });
|
|
74
|
+
return packageId;
|
|
75
|
+
}
|
|
76
|
+
async function renderTemplate(targetRoot, values) {
|
|
77
|
+
await copyTemplateDirectory(fileURLToPath(new URL("../template", import.meta.url)), targetRoot, "", values);
|
|
78
|
+
}
|
|
79
|
+
async function copyTemplateDirectory(templateRoot, targetRoot, relativeDirectory, values) {
|
|
80
|
+
const entries = await readdir(path.join(templateRoot, relativeDirectory), { withFileTypes: true });
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const relativeSource = path.join(relativeDirectory, entry.name);
|
|
83
|
+
const normalizedSource = relativeSource.split(path.sep).join("/");
|
|
84
|
+
const sourcePath = path.join(templateRoot, relativeSource);
|
|
85
|
+
if (entry.isSymbolicLink() || !entry.isDirectory() && !entry.isFile()) throw new Error(`Unsupported template entry: ${normalizedSource}`);
|
|
86
|
+
if (entry.isDirectory()) {
|
|
87
|
+
await copyTemplateDirectory(templateRoot, targetRoot, relativeSource, values);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const outputRelative = templateOutputPath(normalizedSource);
|
|
91
|
+
const outputPath = path.join(targetRoot, ...outputRelative.split("/"));
|
|
92
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
93
|
+
if (normalizedSource.endsWith("gradle-wrapper.jar.base64")) {
|
|
94
|
+
const encoded = (await readFile(sourcePath, "utf8")).replace(/\s+/gu, "");
|
|
95
|
+
await writeFile(outputPath, Buffer.from(encoded, "base64"));
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (TEXT_TEMPLATE_FILES.has(normalizedSource)) {
|
|
99
|
+
await writeFile(outputPath, renderTextTemplate(await readFile(sourcePath, "utf8"), values), "utf8");
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (normalizedSource.endsWith(".tmpl")) throw new Error(`Unregistered text template: ${normalizedSource}`);
|
|
103
|
+
await copyFile(sourcePath, outputPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function templateOutputPath(relativeSource) {
|
|
107
|
+
if (relativeSource === "gitignore") return ".gitignore";
|
|
108
|
+
if (relativeSource.endsWith("gradle-wrapper.jar.base64")) return relativeSource.slice(0, -7);
|
|
109
|
+
return relativeSource.endsWith(".tmpl") ? relativeSource.slice(0, -5) : relativeSource;
|
|
110
|
+
}
|
|
111
|
+
function renderTextTemplate(source, values) {
|
|
112
|
+
const replacements = /* @__PURE__ */ new Map([
|
|
113
|
+
["{{displayNameJson}}", JSON.stringify(values.displayName)],
|
|
114
|
+
["{{projectName}}", values.projectName],
|
|
115
|
+
["{{projectNameJson}}", JSON.stringify(values.projectName)]
|
|
116
|
+
]);
|
|
117
|
+
let rendered = source;
|
|
118
|
+
for (const [token, replacement] of replacements) rendered = rendered.replaceAll(token, replacement);
|
|
119
|
+
const unresolved = rendered.match(/\{\{[A-Za-z][A-Za-z0-9]*\}\}/u);
|
|
120
|
+
if (unresolved !== null) throw new Error(`Unresolved template token: ${unresolved[0]}`);
|
|
121
|
+
return rendered;
|
|
122
|
+
}
|
|
123
|
+
async function writeGeneratedMetadata(projectRoot, config, projectName) {
|
|
124
|
+
const packageJson = {
|
|
125
|
+
name: projectName,
|
|
126
|
+
version: config.jilatax.version,
|
|
127
|
+
private: true,
|
|
128
|
+
type: "module",
|
|
129
|
+
packageManager: "bun@1.3.4",
|
|
130
|
+
engines: { node: ">=22.18.0" },
|
|
131
|
+
scripts: {
|
|
132
|
+
dev: "rspeedy dev",
|
|
133
|
+
build: "rspeedy build",
|
|
134
|
+
typecheck: "tsc -b",
|
|
135
|
+
"run:android": "jilatax run:android",
|
|
136
|
+
"create:aab": "jilatax create:aab"
|
|
137
|
+
},
|
|
138
|
+
dependencies: {
|
|
139
|
+
"@lynx-js/react": LYNX_REACT_VERSION,
|
|
140
|
+
jilatax: JILATAX_VERSION
|
|
141
|
+
},
|
|
142
|
+
devDependencies: {
|
|
143
|
+
"@jilatax/cli": JILATAX_CLI_VERSION,
|
|
144
|
+
"@lynx-js/qrcode-rsbuild-plugin": LYNX_QRCODE_PLUGIN_VERSION,
|
|
145
|
+
"@lynx-js/react-rsbuild-plugin": REACT_PLUGIN_VERSION,
|
|
146
|
+
"@lynx-js/rspeedy": RSPEEDY_VERSION,
|
|
147
|
+
"@lynx-js/types": LYNX_TYPES_VERSION,
|
|
148
|
+
"@types/node": "^22.20.1",
|
|
149
|
+
"@types/react": "^18.3.20",
|
|
150
|
+
typescript: "~5.9.3"
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
await writeFile(path.join(projectRoot, "package.json"), `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
|
|
154
|
+
await writeFile(path.join(projectRoot, "app.json"), `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
155
|
+
await writeFile(path.join(projectRoot, "android", "jilatax.properties"), serializeAndroidProjectConfig(config), "utf8");
|
|
156
|
+
if (process.platform !== "win32") await chmod(path.join(projectRoot, "android", "gradlew"), 493);
|
|
157
|
+
}
|
|
158
|
+
function createInitialConfig(displayName, projectName, packageId) {
|
|
159
|
+
const slug = projectName.replace(/[._-]+/gu, "-").replace(/^-|-$/gu, "");
|
|
160
|
+
const schemeBase = projectName.replace(/[^a-z0-9+.-]+/gu, "-").replace(/^[^a-z]+/u, "");
|
|
161
|
+
return parseAppConfig({
|
|
162
|
+
$schema: "./node_modules/jilatax/schema/app.schema.json",
|
|
163
|
+
jilatax: {
|
|
164
|
+
android: {
|
|
165
|
+
package: packageId,
|
|
166
|
+
predictiveBackGestureEnabled: false,
|
|
167
|
+
versionCode: 1
|
|
168
|
+
},
|
|
169
|
+
name: displayName,
|
|
170
|
+
orientation: "portrait",
|
|
171
|
+
scheme: schemeBase || "jilatax-app",
|
|
172
|
+
slug,
|
|
173
|
+
splash: { backgroundColor: "#0F172A" },
|
|
174
|
+
userInterfaceStyle: "automatic",
|
|
175
|
+
version: "1.0.0"
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async function assertTargetDoesNotExist(target) {
|
|
180
|
+
try {
|
|
181
|
+
await lstat(target);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (error.code === "ENOENT") return;
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
throw new Error(`Target directory already exists: ${target}`);
|
|
187
|
+
}
|
|
188
|
+
function titleCase$1(value) {
|
|
189
|
+
return value.split(/[-_.\s]+/u).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
190
|
+
}
|
|
191
|
+
function installWithBun(projectDirectory) {
|
|
192
|
+
return new Promise((resolveInstall, rejectInstall) => {
|
|
193
|
+
const child = spawn("bun", ["install"], {
|
|
194
|
+
cwd: projectDirectory,
|
|
195
|
+
env: process.env,
|
|
196
|
+
stdio: "inherit",
|
|
197
|
+
windowsHide: true
|
|
198
|
+
});
|
|
199
|
+
child.once("error", rejectInstall);
|
|
200
|
+
child.once("close", (code) => {
|
|
201
|
+
if (code === 0) {
|
|
202
|
+
resolveInstall();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
rejectInstall(/* @__PURE__ */ new Error(`bun install failed with exit code ${code ?? 1}.`));
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/cli.ts
|
|
211
|
+
var PromptCancelledError = class extends Error {};
|
|
212
|
+
async function runCreateCli(args = process.argv.slice(2), services = {}) {
|
|
213
|
+
const log = services.log ?? console.log;
|
|
214
|
+
const warn = services.warn ?? console.error;
|
|
215
|
+
try {
|
|
216
|
+
const parsed = parseArgs(args);
|
|
217
|
+
if (parsed.showHelp) {
|
|
218
|
+
log(createHelpText());
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
if (parsed.showVersion) {
|
|
222
|
+
log(await readCreatorVersion());
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
const options = services.interactive ?? (stdin.isTTY && stdout.isTTY) ? await promptForOptions(parsed) : nonInteractiveOptions(parsed);
|
|
226
|
+
printResult(await (services.create ?? createProject)({
|
|
227
|
+
displayName: options.displayName,
|
|
228
|
+
install: options.install,
|
|
229
|
+
packageId: options.packageId,
|
|
230
|
+
targetDirectory: options.targetDirectory
|
|
231
|
+
}), log);
|
|
232
|
+
return 0;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (error instanceof PromptCancelledError) return 0;
|
|
235
|
+
warn(error instanceof Error ? error.message : String(error));
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function createHelpText() {
|
|
240
|
+
return `Create an Android-first Jilatax application.
|
|
241
|
+
|
|
242
|
+
Usage:
|
|
243
|
+
create-jilatax [directory] [options]
|
|
244
|
+
|
|
245
|
+
Options:
|
|
246
|
+
--name <display-name> Human-readable application name
|
|
247
|
+
--package-id <id> Android application ID
|
|
248
|
+
--install Install dependencies with Bun
|
|
249
|
+
--skip-install Generate without installing dependencies
|
|
250
|
+
-h, --help Show this help
|
|
251
|
+
-V, --version Show the installed creator version`;
|
|
252
|
+
}
|
|
253
|
+
function parseArgs(args) {
|
|
254
|
+
let displayName;
|
|
255
|
+
let install;
|
|
256
|
+
let packageId;
|
|
257
|
+
let showHelp = false;
|
|
258
|
+
let showVersion = false;
|
|
259
|
+
let targetDirectory;
|
|
260
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
261
|
+
const argument = args[index];
|
|
262
|
+
if (argument === void 0) continue;
|
|
263
|
+
if (argument === "--install") install = true;
|
|
264
|
+
else if (argument === "--skip-install" || argument === "--no-install") install = false;
|
|
265
|
+
else if (argument === "--name") {
|
|
266
|
+
displayName = readOptionValue(args, index, argument);
|
|
267
|
+
index += 1;
|
|
268
|
+
} else if (argument.startsWith("--name=")) displayName = readInlineValue(argument, "--name");
|
|
269
|
+
else if (argument === "--package-id") {
|
|
270
|
+
packageId = readOptionValue(args, index, argument);
|
|
271
|
+
index += 1;
|
|
272
|
+
} else if (argument.startsWith("--package-id=")) packageId = readInlineValue(argument, "--package-id");
|
|
273
|
+
else if (argument === "--help" || argument === "-h") showHelp = true;
|
|
274
|
+
else if (argument === "--version" || argument === "-V") showVersion = true;
|
|
275
|
+
else if (argument.startsWith("-")) throw new Error(`Unknown option: ${argument}`);
|
|
276
|
+
else if (targetDirectory === void 0) targetDirectory = argument;
|
|
277
|
+
else throw new Error(`Unexpected argument: ${argument}`);
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
...displayName === void 0 ? {} : { displayName },
|
|
281
|
+
...install === void 0 ? {} : { install },
|
|
282
|
+
...packageId === void 0 ? {} : { packageId },
|
|
283
|
+
showHelp,
|
|
284
|
+
showVersion,
|
|
285
|
+
...targetDirectory === void 0 ? {} : { targetDirectory }
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async function promptForOptions(options) {
|
|
289
|
+
prompts.intro("Jilatax ยท Android-first Lynx application");
|
|
290
|
+
const targetDirectory = options.targetDirectory ?? unwrapPrompt(await prompts.text({
|
|
291
|
+
message: "Where should the project be created?",
|
|
292
|
+
placeholder: "./my-jilatax-app",
|
|
293
|
+
validate(value) {
|
|
294
|
+
try {
|
|
295
|
+
normalizeProjectNameFromDirectory(value ?? "");
|
|
296
|
+
} catch (error) {
|
|
297
|
+
return validationMessage(error);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}));
|
|
301
|
+
const projectName = normalizeProjectNameFromDirectory(targetDirectory);
|
|
302
|
+
const displayName = options.displayName ?? unwrapPrompt(await prompts.text({
|
|
303
|
+
initialValue: titleCase(projectName),
|
|
304
|
+
message: "Application name",
|
|
305
|
+
validate(value) {
|
|
306
|
+
try {
|
|
307
|
+
normalizeDisplayName(value ?? "");
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return validationMessage(error);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}));
|
|
313
|
+
const packageId = options.packageId ?? unwrapPrompt(await prompts.text({
|
|
314
|
+
initialValue: defaultPackageId(projectName),
|
|
315
|
+
message: "Android application ID",
|
|
316
|
+
validate(value) {
|
|
317
|
+
try {
|
|
318
|
+
validatePackageId(value ?? "");
|
|
319
|
+
} catch (error) {
|
|
320
|
+
return validationMessage(error);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}));
|
|
324
|
+
const install = options.install ?? unwrapPrompt(await prompts.confirm({
|
|
325
|
+
initialValue: true,
|
|
326
|
+
message: "Install dependencies with Bun?"
|
|
327
|
+
}));
|
|
328
|
+
return {
|
|
329
|
+
displayName: normalizeDisplayName(displayName),
|
|
330
|
+
install,
|
|
331
|
+
packageId: validatePackageId(packageId),
|
|
332
|
+
targetDirectory
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function nonInteractiveOptions(options) {
|
|
336
|
+
if (options.targetDirectory === void 0) throw new Error("Project directory is required in a non-interactive terminal.");
|
|
337
|
+
const projectName = normalizeProjectNameFromDirectory(options.targetDirectory);
|
|
338
|
+
return {
|
|
339
|
+
displayName: normalizeDisplayName(options.displayName ?? titleCase(projectName)),
|
|
340
|
+
install: options.install ?? false,
|
|
341
|
+
packageId: validatePackageId(options.packageId ?? defaultPackageId(projectName)),
|
|
342
|
+
targetDirectory: options.targetDirectory
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function normalizeProjectNameFromDirectory(directory) {
|
|
346
|
+
return normalizeProjectName(directory.trim().replace(/[\\/]+$/gu, "").split(/[\\/]/u).at(-1) ?? "");
|
|
347
|
+
}
|
|
348
|
+
function unwrapPrompt(value) {
|
|
349
|
+
if (prompts.isCancel(value)) {
|
|
350
|
+
prompts.cancel("Project creation cancelled.");
|
|
351
|
+
throw new PromptCancelledError();
|
|
352
|
+
}
|
|
353
|
+
return value;
|
|
354
|
+
}
|
|
355
|
+
function validationMessage(error) {
|
|
356
|
+
return error instanceof Error ? error.message : String(error);
|
|
357
|
+
}
|
|
358
|
+
function readOptionValue(args, index, option) {
|
|
359
|
+
const value = args[index + 1];
|
|
360
|
+
if (value === void 0 || value.startsWith("-") || value.trim().length === 0) throw new Error(`${option} requires a value.`);
|
|
361
|
+
return value.trim();
|
|
362
|
+
}
|
|
363
|
+
function readInlineValue(argument, option) {
|
|
364
|
+
const value = argument.slice(`${option}=`.length).trim();
|
|
365
|
+
if (value.length === 0) throw new Error(`${option} requires a value.`);
|
|
366
|
+
return value;
|
|
367
|
+
}
|
|
368
|
+
function titleCase(value) {
|
|
369
|
+
return value.split(/[-_.\s]+/u).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
370
|
+
}
|
|
371
|
+
function printResult(result, log) {
|
|
372
|
+
const installStep = result.installed ? "" : " bun install\n";
|
|
373
|
+
log(`Created ${result.displayName} in ${result.projectDirectory}.
|
|
374
|
+
|
|
375
|
+
Next steps:
|
|
376
|
+
cd ${JSON.stringify(result.projectDirectory)}
|
|
377
|
+
${installStep} bun run run:android
|
|
378
|
+
|
|
379
|
+
Release bundle:
|
|
380
|
+
bun run create:aab`);
|
|
381
|
+
}
|
|
382
|
+
async function readCreatorVersion() {
|
|
383
|
+
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
384
|
+
return typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
385
|
+
}
|
|
386
|
+
//#endregion
|
|
387
|
+
export { normalizeDisplayName as a, defaultPackageId as i, runCreateCli as n, normalizeProjectName as o, createProject as r, validatePackageId as s, createHelpText as t };
|