create-jilatax 0.0.2 → 0.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.
Files changed (43) hide show
  1. package/README.md +38 -9
  2. package/THIRD_PARTY_NOTICES.md +7 -0
  3. package/dist/bin.cjs +6 -0
  4. package/dist/bin.d.cts +1 -0
  5. package/dist/bin.d.ts +1 -0
  6. package/dist/bin.js +8 -0
  7. package/dist/cli-BO6toXiq.cjs +450 -0
  8. package/dist/cli-BQK3vkvN.js +385 -0
  9. package/dist/index.cjs +8 -6
  10. package/dist/index.d.cts +32 -3
  11. package/dist/index.d.ts +32 -3
  12. package/dist/index.js +2 -6
  13. package/package.json +13 -5
  14. package/template/README.md.tmpl +31 -0
  15. package/template/android/app/build.gradle.kts +112 -0
  16. package/template/android/app/proguard-rules.pro +1 -0
  17. package/template/android/app/src/debug/AndroidManifest.xml +8 -0
  18. package/template/android/app/src/main/AndroidManifest.xml +30 -0
  19. package/template/android/app/src/main/res/drawable/jilatax_mark_foreground.xml +11 -0
  20. package/template/android/app/src/main/res/drawable/jilatax_splash.xml +9 -0
  21. package/template/android/app/src/main/res/mipmap-anydpi/jilatax_launcher.xml +12 -0
  22. package/template/android/app/src/main/res/mipmap-anydpi/jilatax_launcher_round.xml +12 -0
  23. package/template/android/app/src/main/res/mipmap-anydpi-v26/jilatax_launcher.xml +5 -0
  24. package/template/android/app/src/main/res/mipmap-anydpi-v26/jilatax_launcher_round.xml +5 -0
  25. package/template/android/app/src/main/res/values/styles.xml +16 -0
  26. package/template/android/app/src/main/res/values-v31/styles.xml +8 -0
  27. package/template/android/build.gradle.kts +5 -0
  28. package/template/android/gradle/wrapper/gradle-wrapper.jar.base64 +1112 -0
  29. package/template/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  30. package/template/android/gradle.properties +6 -0
  31. package/template/android/gradlew +248 -0
  32. package/template/android/gradlew.bat +92 -0
  33. package/template/android/keystore.properties.example +5 -0
  34. package/template/android/settings.gradle.kts.tmpl +29 -0
  35. package/template/gitignore +22 -0
  36. package/template/lynx.config.ts +17 -0
  37. package/template/src/App.css +95 -0
  38. package/template/src/App.tsx.tmpl +23 -0
  39. package/template/src/index.tsx +9 -0
  40. package/template/src/rspeedy-env.d.ts +1 -0
  41. package/template/src/tsconfig.json +13 -0
  42. package/template/tsconfig.json +15 -0
  43. package/template/tsconfig.node.json +12 -0
@@ -0,0 +1,385 @@
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.7";
10
+ const JILATAX_VERSION = "^0.0.5";
11
+ const LYNX_REACT_VERSION = "^0.116.2";
12
+ const LYNX_TYPES_VERSION = "^3.7.0";
13
+ const REACT_PLUGIN_VERSION = "^0.12.7";
14
+ const RSPEEDY_VERSION = "^0.13.3";
15
+ const TEXT_TEMPLATE_FILES = /* @__PURE__ */ new Set([
16
+ "README.md.tmpl",
17
+ "android/settings.gradle.kts.tmpl",
18
+ "src/App.tsx.tmpl"
19
+ ]);
20
+ async function createProject(options) {
21
+ const projectDirectory = path.resolve(options.targetDirectory);
22
+ const projectName = normalizeProjectName(path.basename(projectDirectory));
23
+ const displayName = normalizeDisplayName(options.displayName ?? titleCase$1(projectName));
24
+ const packageId = validatePackageId(options.packageId ?? defaultPackageId(projectName));
25
+ const config = createInitialConfig(displayName, projectName, packageId);
26
+ await assertTargetDoesNotExist(projectDirectory);
27
+ const parentDirectory = path.dirname(projectDirectory);
28
+ await mkdir(parentDirectory, { recursive: true });
29
+ const stagingDirectory = await mkdtemp(path.join(parentDirectory, `.${projectName}-`));
30
+ try {
31
+ await renderTemplate(stagingDirectory, {
32
+ displayName,
33
+ projectName
34
+ });
35
+ await writeGeneratedMetadata(stagingDirectory, config, projectName);
36
+ await rename(stagingDirectory, projectDirectory);
37
+ } catch (error) {
38
+ await rm(stagingDirectory, {
39
+ force: true,
40
+ recursive: true
41
+ });
42
+ throw error;
43
+ }
44
+ const shouldInstall = options.install === true;
45
+ if (shouldInstall) await (options.installer ?? installWithBun)(projectDirectory);
46
+ return {
47
+ displayName,
48
+ installed: shouldInstall,
49
+ packageId,
50
+ projectDirectory,
51
+ projectName
52
+ };
53
+ }
54
+ function normalizeProjectName(value) {
55
+ const projectName = value.trim().toLowerCase();
56
+ 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.");
57
+ return projectName;
58
+ }
59
+ function normalizeDisplayName(value) {
60
+ const displayName = value.trim().replace(/\s+/gu, " ");
61
+ if (displayName.length === 0 || displayName.length > 80) throw new Error("Application name must contain between 1 and 80 characters.");
62
+ return displayName;
63
+ }
64
+ function defaultPackageId(projectName) {
65
+ return `com.example.${projectName.replace(/[^a-z0-9_]+/gu, "_").replace(/^[^a-z]+/u, "").replace(/_+$/gu, "") || "app"}`;
66
+ }
67
+ function validatePackageId(value) {
68
+ const packageId = value.trim();
69
+ parseAppConfig({ jilatax: {
70
+ android: { package: packageId },
71
+ name: "Package validation"
72
+ } });
73
+ return packageId;
74
+ }
75
+ async function renderTemplate(targetRoot, values) {
76
+ await copyTemplateDirectory(fileURLToPath(new URL("../template", import.meta.url)), targetRoot, "", values);
77
+ }
78
+ async function copyTemplateDirectory(templateRoot, targetRoot, relativeDirectory, values) {
79
+ const entries = await readdir(path.join(templateRoot, relativeDirectory), { withFileTypes: true });
80
+ for (const entry of entries) {
81
+ const relativeSource = path.join(relativeDirectory, entry.name);
82
+ const normalizedSource = relativeSource.split(path.sep).join("/");
83
+ const sourcePath = path.join(templateRoot, relativeSource);
84
+ if (entry.isSymbolicLink() || !entry.isDirectory() && !entry.isFile()) throw new Error(`Unsupported template entry: ${normalizedSource}`);
85
+ if (entry.isDirectory()) {
86
+ await copyTemplateDirectory(templateRoot, targetRoot, relativeSource, values);
87
+ continue;
88
+ }
89
+ const outputRelative = templateOutputPath(normalizedSource);
90
+ const outputPath = path.join(targetRoot, ...outputRelative.split("/"));
91
+ await mkdir(path.dirname(outputPath), { recursive: true });
92
+ if (normalizedSource.endsWith("gradle-wrapper.jar.base64")) {
93
+ const encoded = (await readFile(sourcePath, "utf8")).replace(/\s+/gu, "");
94
+ await writeFile(outputPath, Buffer.from(encoded, "base64"));
95
+ continue;
96
+ }
97
+ if (TEXT_TEMPLATE_FILES.has(normalizedSource)) {
98
+ await writeFile(outputPath, renderTextTemplate(await readFile(sourcePath, "utf8"), values), "utf8");
99
+ continue;
100
+ }
101
+ if (normalizedSource.endsWith(".tmpl")) throw new Error(`Unregistered text template: ${normalizedSource}`);
102
+ await copyFile(sourcePath, outputPath);
103
+ }
104
+ }
105
+ function templateOutputPath(relativeSource) {
106
+ if (relativeSource === "gitignore") return ".gitignore";
107
+ if (relativeSource.endsWith("gradle-wrapper.jar.base64")) return relativeSource.slice(0, -7);
108
+ return relativeSource.endsWith(".tmpl") ? relativeSource.slice(0, -5) : relativeSource;
109
+ }
110
+ function renderTextTemplate(source, values) {
111
+ const replacements = /* @__PURE__ */ new Map([
112
+ ["{{displayNameJson}}", JSON.stringify(values.displayName)],
113
+ ["{{projectName}}", values.projectName],
114
+ ["{{projectNameJson}}", JSON.stringify(values.projectName)]
115
+ ]);
116
+ let rendered = source;
117
+ for (const [token, replacement] of replacements) rendered = rendered.replaceAll(token, replacement);
118
+ const unresolved = rendered.match(/\{\{[A-Za-z][A-Za-z0-9]*\}\}/u);
119
+ if (unresolved !== null) throw new Error(`Unresolved template token: ${unresolved[0]}`);
120
+ return rendered;
121
+ }
122
+ async function writeGeneratedMetadata(projectRoot, config, projectName) {
123
+ const packageJson = {
124
+ name: projectName,
125
+ version: config.jilatax.version,
126
+ private: true,
127
+ type: "module",
128
+ packageManager: "bun@1.3.4",
129
+ engines: { node: ">=22.18.0" },
130
+ scripts: {
131
+ dev: "rspeedy dev",
132
+ build: "rspeedy build",
133
+ typecheck: "tsc -b",
134
+ "run:android": "jilatax run:android",
135
+ "create:aab": "jilatax create:aab"
136
+ },
137
+ dependencies: {
138
+ "@lynx-js/react": LYNX_REACT_VERSION,
139
+ jilatax: JILATAX_VERSION
140
+ },
141
+ devDependencies: {
142
+ "@jilatax/cli": JILATAX_CLI_VERSION,
143
+ "@lynx-js/react-rsbuild-plugin": REACT_PLUGIN_VERSION,
144
+ "@lynx-js/rspeedy": RSPEEDY_VERSION,
145
+ "@lynx-js/types": LYNX_TYPES_VERSION,
146
+ "@types/node": "^22.20.1",
147
+ "@types/react": "^18.3.20",
148
+ typescript: "~5.9.3"
149
+ }
150
+ };
151
+ await writeFile(path.join(projectRoot, "package.json"), `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
152
+ await writeFile(path.join(projectRoot, "app.json"), `${JSON.stringify(config, null, 2)}\n`, "utf8");
153
+ await writeFile(path.join(projectRoot, "android", "jilatax.properties"), serializeAndroidProjectConfig(config), "utf8");
154
+ if (process.platform !== "win32") await chmod(path.join(projectRoot, "android", "gradlew"), 493);
155
+ }
156
+ function createInitialConfig(displayName, projectName, packageId) {
157
+ const slug = projectName.replace(/[._-]+/gu, "-").replace(/^-|-$/gu, "");
158
+ const schemeBase = projectName.replace(/[^a-z0-9+.-]+/gu, "-").replace(/^[^a-z]+/u, "");
159
+ return parseAppConfig({
160
+ $schema: "./node_modules/jilatax/schema/app.schema.json",
161
+ jilatax: {
162
+ android: {
163
+ package: packageId,
164
+ predictiveBackGestureEnabled: false,
165
+ versionCode: 1
166
+ },
167
+ name: displayName,
168
+ orientation: "portrait",
169
+ scheme: schemeBase || "jilatax-app",
170
+ slug,
171
+ splash: { backgroundColor: "#0F172A" },
172
+ userInterfaceStyle: "automatic",
173
+ version: "1.0.0"
174
+ }
175
+ });
176
+ }
177
+ async function assertTargetDoesNotExist(target) {
178
+ try {
179
+ await lstat(target);
180
+ } catch (error) {
181
+ if (error.code === "ENOENT") return;
182
+ throw error;
183
+ }
184
+ throw new Error(`Target directory already exists: ${target}`);
185
+ }
186
+ function titleCase$1(value) {
187
+ return value.split(/[-_.\s]+/u).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
188
+ }
189
+ function installWithBun(projectDirectory) {
190
+ return new Promise((resolveInstall, rejectInstall) => {
191
+ const child = spawn("bun", ["install"], {
192
+ cwd: projectDirectory,
193
+ env: process.env,
194
+ stdio: "inherit",
195
+ windowsHide: true
196
+ });
197
+ child.once("error", rejectInstall);
198
+ child.once("close", (code) => {
199
+ if (code === 0) {
200
+ resolveInstall();
201
+ return;
202
+ }
203
+ rejectInstall(/* @__PURE__ */ new Error(`bun install failed with exit code ${code ?? 1}.`));
204
+ });
205
+ });
206
+ }
207
+ //#endregion
208
+ //#region src/cli.ts
209
+ var PromptCancelledError = class extends Error {};
210
+ async function runCreateCli(args = process.argv.slice(2), services = {}) {
211
+ const log = services.log ?? console.log;
212
+ const warn = services.warn ?? console.error;
213
+ try {
214
+ const parsed = parseArgs(args);
215
+ if (parsed.showHelp) {
216
+ log(createHelpText());
217
+ return 0;
218
+ }
219
+ if (parsed.showVersion) {
220
+ log(await readCreatorVersion());
221
+ return 0;
222
+ }
223
+ const options = services.interactive ?? (stdin.isTTY && stdout.isTTY) ? await promptForOptions(parsed) : nonInteractiveOptions(parsed);
224
+ printResult(await (services.create ?? createProject)({
225
+ displayName: options.displayName,
226
+ install: options.install,
227
+ packageId: options.packageId,
228
+ targetDirectory: options.targetDirectory
229
+ }), log);
230
+ return 0;
231
+ } catch (error) {
232
+ if (error instanceof PromptCancelledError) return 0;
233
+ warn(error instanceof Error ? error.message : String(error));
234
+ return 1;
235
+ }
236
+ }
237
+ function createHelpText() {
238
+ return `Create an Android-first Jilatax application.
239
+
240
+ Usage:
241
+ create-jilatax [directory] [options]
242
+
243
+ Options:
244
+ --name <display-name> Human-readable application name
245
+ --package-id <id> Android application ID
246
+ --install Install dependencies with Bun
247
+ --skip-install Generate without installing dependencies
248
+ -h, --help Show this help
249
+ -V, --version Show the installed creator version`;
250
+ }
251
+ function parseArgs(args) {
252
+ let displayName;
253
+ let install;
254
+ let packageId;
255
+ let showHelp = false;
256
+ let showVersion = false;
257
+ let targetDirectory;
258
+ for (let index = 0; index < args.length; index += 1) {
259
+ const argument = args[index];
260
+ if (argument === void 0) continue;
261
+ if (argument === "--install") install = true;
262
+ else if (argument === "--skip-install" || argument === "--no-install") install = false;
263
+ else if (argument === "--name") {
264
+ displayName = readOptionValue(args, index, argument);
265
+ index += 1;
266
+ } else if (argument.startsWith("--name=")) displayName = readInlineValue(argument, "--name");
267
+ else if (argument === "--package-id") {
268
+ packageId = readOptionValue(args, index, argument);
269
+ index += 1;
270
+ } else if (argument.startsWith("--package-id=")) packageId = readInlineValue(argument, "--package-id");
271
+ else if (argument === "--help" || argument === "-h") showHelp = true;
272
+ else if (argument === "--version" || argument === "-V") showVersion = true;
273
+ else if (argument.startsWith("-")) throw new Error(`Unknown option: ${argument}`);
274
+ else if (targetDirectory === void 0) targetDirectory = argument;
275
+ else throw new Error(`Unexpected argument: ${argument}`);
276
+ }
277
+ return {
278
+ ...displayName === void 0 ? {} : { displayName },
279
+ ...install === void 0 ? {} : { install },
280
+ ...packageId === void 0 ? {} : { packageId },
281
+ showHelp,
282
+ showVersion,
283
+ ...targetDirectory === void 0 ? {} : { targetDirectory }
284
+ };
285
+ }
286
+ async function promptForOptions(options) {
287
+ prompts.intro("Jilatax · Android-first Lynx application");
288
+ const targetDirectory = options.targetDirectory ?? unwrapPrompt(await prompts.text({
289
+ message: "Where should the project be created?",
290
+ placeholder: "./my-jilatax-app",
291
+ validate(value) {
292
+ try {
293
+ normalizeProjectNameFromDirectory(value ?? "");
294
+ } catch (error) {
295
+ return validationMessage(error);
296
+ }
297
+ }
298
+ }));
299
+ const projectName = normalizeProjectNameFromDirectory(targetDirectory);
300
+ const displayName = options.displayName ?? unwrapPrompt(await prompts.text({
301
+ initialValue: titleCase(projectName),
302
+ message: "Application name",
303
+ validate(value) {
304
+ try {
305
+ normalizeDisplayName(value ?? "");
306
+ } catch (error) {
307
+ return validationMessage(error);
308
+ }
309
+ }
310
+ }));
311
+ const packageId = options.packageId ?? unwrapPrompt(await prompts.text({
312
+ initialValue: defaultPackageId(projectName),
313
+ message: "Android application ID",
314
+ validate(value) {
315
+ try {
316
+ validatePackageId(value ?? "");
317
+ } catch (error) {
318
+ return validationMessage(error);
319
+ }
320
+ }
321
+ }));
322
+ const install = options.install ?? unwrapPrompt(await prompts.confirm({
323
+ initialValue: true,
324
+ message: "Install dependencies with Bun?"
325
+ }));
326
+ return {
327
+ displayName: normalizeDisplayName(displayName),
328
+ install,
329
+ packageId: validatePackageId(packageId),
330
+ targetDirectory
331
+ };
332
+ }
333
+ function nonInteractiveOptions(options) {
334
+ if (options.targetDirectory === void 0) throw new Error("Project directory is required in a non-interactive terminal.");
335
+ const projectName = normalizeProjectNameFromDirectory(options.targetDirectory);
336
+ return {
337
+ displayName: normalizeDisplayName(options.displayName ?? titleCase(projectName)),
338
+ install: options.install ?? false,
339
+ packageId: validatePackageId(options.packageId ?? defaultPackageId(projectName)),
340
+ targetDirectory: options.targetDirectory
341
+ };
342
+ }
343
+ function normalizeProjectNameFromDirectory(directory) {
344
+ return normalizeProjectName(directory.trim().replace(/[\\/]+$/gu, "").split(/[\\/]/u).at(-1) ?? "");
345
+ }
346
+ function unwrapPrompt(value) {
347
+ if (prompts.isCancel(value)) {
348
+ prompts.cancel("Project creation cancelled.");
349
+ throw new PromptCancelledError();
350
+ }
351
+ return value;
352
+ }
353
+ function validationMessage(error) {
354
+ return error instanceof Error ? error.message : String(error);
355
+ }
356
+ function readOptionValue(args, index, option) {
357
+ const value = args[index + 1];
358
+ if (value === void 0 || value.startsWith("-") || value.trim().length === 0) throw new Error(`${option} requires a value.`);
359
+ return value.trim();
360
+ }
361
+ function readInlineValue(argument, option) {
362
+ const value = argument.slice(`${option}=`.length).trim();
363
+ if (value.length === 0) throw new Error(`${option} requires a value.`);
364
+ return value;
365
+ }
366
+ function titleCase(value) {
367
+ return value.split(/[-_.\s]+/u).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
368
+ }
369
+ function printResult(result, log) {
370
+ const installStep = result.installed ? "" : " bun install\n";
371
+ log(`Created ${result.displayName} in ${result.projectDirectory}.
372
+
373
+ Next steps:
374
+ cd ${JSON.stringify(result.projectDirectory)}
375
+ ${installStep} bun run run:android
376
+
377
+ Release bundle:
378
+ bun run create:aab`);
379
+ }
380
+ async function readCreatorVersion() {
381
+ const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
382
+ return typeof packageJson.version === "string" ? packageJson.version : "unknown";
383
+ }
384
+ //#endregion
385
+ export { normalizeDisplayName as a, defaultPackageId as i, runCreateCli as n, normalizeProjectName as o, createProject as r, validatePackageId as s, createHelpText as t };
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region src/index.ts
3
- function hello(name) {
4
- return `Hello, ${name}!`;
5
- }
6
- //#endregion
7
- exports.hello = hello;
2
+ const require_cli = require("./cli-BO6toXiq.cjs");
3
+ exports.createHelpText = require_cli.createHelpText;
4
+ exports.createProject = require_cli.createProject;
5
+ exports.defaultPackageId = require_cli.defaultPackageId;
6
+ exports.normalizeDisplayName = require_cli.normalizeDisplayName;
7
+ exports.normalizeProjectName = require_cli.normalizeProjectName;
8
+ exports.runCreateCli = require_cli.runCreateCli;
9
+ exports.validatePackageId = require_cli.validatePackageId;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,33 @@
1
- //#region src/index.d.ts
2
- declare function hello(name: string): string;
1
+ //#region src/generator.d.ts
2
+ interface CreateProjectOptions {
3
+ readonly displayName?: string;
4
+ readonly install?: boolean;
5
+ readonly installer?: ProjectInstaller;
6
+ readonly packageId?: string;
7
+ readonly targetDirectory: string;
8
+ }
9
+ interface CreateProjectResult {
10
+ readonly displayName: string;
11
+ readonly installed: boolean;
12
+ readonly packageId: string;
13
+ readonly projectDirectory: string;
14
+ readonly projectName: string;
15
+ }
16
+ type ProjectInstaller = (projectDirectory: string) => Promise<void>;
17
+ declare function createProject(options: CreateProjectOptions): Promise<CreateProjectResult>;
18
+ declare function normalizeProjectName(value: string): string;
19
+ declare function normalizeDisplayName(value: string): string;
20
+ declare function defaultPackageId(projectName: string): string;
21
+ declare function validatePackageId(value: string): string;
3
22
  //#endregion
4
- export { hello };
23
+ //#region src/cli.d.ts
24
+ interface CreateCliServices {
25
+ readonly create?: typeof createProject;
26
+ readonly interactive?: boolean;
27
+ readonly log?: (message: string) => void;
28
+ readonly warn?: (message: string) => void;
29
+ }
30
+ declare function runCreateCli(args?: readonly string[], services?: CreateCliServices): Promise<number>;
31
+ declare function createHelpText(): string;
32
+ //#endregion
33
+ export { type CreateCliServices, type CreateProjectOptions, type CreateProjectResult, type ProjectInstaller, createHelpText, createProject, defaultPackageId, normalizeDisplayName, normalizeProjectName, runCreateCli, validatePackageId };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,33 @@
1
- //#region src/index.d.ts
2
- declare function hello(name: string): string;
1
+ //#region src/generator.d.ts
2
+ interface CreateProjectOptions {
3
+ readonly displayName?: string;
4
+ readonly install?: boolean;
5
+ readonly installer?: ProjectInstaller;
6
+ readonly packageId?: string;
7
+ readonly targetDirectory: string;
8
+ }
9
+ interface CreateProjectResult {
10
+ readonly displayName: string;
11
+ readonly installed: boolean;
12
+ readonly packageId: string;
13
+ readonly projectDirectory: string;
14
+ readonly projectName: string;
15
+ }
16
+ type ProjectInstaller = (projectDirectory: string) => Promise<void>;
17
+ declare function createProject(options: CreateProjectOptions): Promise<CreateProjectResult>;
18
+ declare function normalizeProjectName(value: string): string;
19
+ declare function normalizeDisplayName(value: string): string;
20
+ declare function defaultPackageId(projectName: string): string;
21
+ declare function validatePackageId(value: string): string;
3
22
  //#endregion
4
- export { hello };
23
+ //#region src/cli.d.ts
24
+ interface CreateCliServices {
25
+ readonly create?: typeof createProject;
26
+ readonly interactive?: boolean;
27
+ readonly log?: (message: string) => void;
28
+ readonly warn?: (message: string) => void;
29
+ }
30
+ declare function runCreateCli(args?: readonly string[], services?: CreateCliServices): Promise<number>;
31
+ declare function createHelpText(): string;
32
+ //#endregion
33
+ export { type CreateCliServices, type CreateProjectOptions, type CreateProjectResult, type ProjectInstaller, createHelpText, createProject, defaultPackageId, normalizeDisplayName, normalizeProjectName, runCreateCli, validatePackageId };
package/dist/index.js CHANGED
@@ -1,6 +1,2 @@
1
- //#region src/index.ts
2
- function hello(name) {
3
- return `Hello, ${name}!`;
4
- }
5
- //#endregion
6
- export { hello };
1
+ import { a as normalizeDisplayName, i as defaultPackageId, n as runCreateCli, o as normalizeProjectName, r as createProject, s as validatePackageId, t as createHelpText } from "./cli-BQK3vkvN.js";
2
+ export { createHelpText, createProject, defaultPackageId, normalizeDisplayName, normalizeProjectName, runCreateCli, validatePackageId };
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "create-jilatax",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "packageManager": "bun@1.3.4",
5
- "description": "A minimal TypeScript library package with ESM, CommonJS, and declaration outputs.",
5
+ "description": "Create an Android-first Jilatax application for Lynx and Rspeedy.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",
8
8
  "module": "./dist/index.js",
9
9
  "types": "./dist/index.d.ts",
10
+ "bin": {
11
+ "create-jilatax": "./dist/bin.js"
12
+ },
10
13
  "exports": {
11
14
  ".": {
12
15
  "import": {
@@ -19,10 +22,10 @@
19
22
  }
20
23
  }
21
24
  },
22
- "files": ["dist"],
23
- "sideEffects": false,
25
+ "files": ["dist", "template", "THIRD_PARTY_NOTICES.md"],
26
+ "sideEffects": ["./dist/bin.js"],
24
27
 
25
- "keywords": ["typescript", "esm", "commonjs"],
28
+ "keywords": ["android", "creator", "jilatax", "lynx", "rspeedy"],
26
29
  "author": "Gohit X (bastndev)",
27
30
  "license": "MIT",
28
31
  "homepage": "https://jilatax.dev/jilatax",
@@ -40,6 +43,11 @@
40
43
  "node": ">=22.18.0"
41
44
  },
42
45
 
46
+ "dependencies": {
47
+ "@clack/prompts": "^1.7.0",
48
+ "jilatax": "^0.0.5"
49
+ },
50
+
43
51
  "devDependencies": {
44
52
  "@arethetypeswrong/core": "^0.18.5",
45
53
  "@types/node": "^22.20.1",
@@ -0,0 +1,31 @@
1
+ # {{projectName}}
2
+
3
+ Android-first Lynx application created with Jilatax.
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ bun install
9
+ bun run run:android
10
+ ```
11
+
12
+ The Android command starts Rspeedy, builds and installs the debug APK, verifies
13
+ the ADB reverse tunnel, and launches the app once.
14
+
15
+ To run only the Lynx development server:
16
+
17
+ ```bash
18
+ bun run dev
19
+ ```
20
+
21
+ ## Android App Bundle
22
+
23
+ ```bash
24
+ bun run create:aab
25
+ ```
26
+
27
+ Configure `android/keystore.properties` from the included example before a
28
+ Play Store release. Never commit signing credentials.
29
+
30
+ Application metadata lives in `app.json`. Android is the only supported native
31
+ platform in this phase.
@@ -0,0 +1,112 @@
1
+ import java.util.Properties
2
+ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
3
+
4
+ plugins {
5
+ id("com.android.application")
6
+ id("org.jetbrains.kotlin.android")
7
+ }
8
+
9
+ val jilataxConfigFile = rootProject.file("jilatax.properties")
10
+ check(jilataxConfigFile.isFile) {
11
+ "Missing android/jilatax.properties. Run a Jilatax Android command from the project root."
12
+ }
13
+ val jilataxConfig = Properties().apply {
14
+ jilataxConfigFile.reader(Charsets.UTF_8).use(::load)
15
+ }
16
+
17
+ fun requiredConfig(key: String): String =
18
+ checkNotNull(jilataxConfig.getProperty(key)?.takeIf { it.isNotBlank() }) {
19
+ "Missing Jilatax Android property: $key"
20
+ }
21
+
22
+ val appName = requiredConfig("jilatax.name")
23
+ val appPackage = requiredConfig("jilatax.android.package")
24
+ val appVersion = requiredConfig("jilatax.version")
25
+ val appVersionCode = requiredConfig("jilatax.android.versionCode").toInt()
26
+ val appScheme = requiredConfig("jilatax.scheme")
27
+ val predictiveBack = requiredConfig(
28
+ "jilatax.android.predictiveBackGestureEnabled",
29
+ ).toBooleanStrict()
30
+ val screenOrientation =
31
+ when (requiredConfig("jilatax.orientation")) {
32
+ "portrait" -> "portrait"
33
+ "landscape" -> "landscape"
34
+ else -> "unspecified"
35
+ }
36
+ val splashBackground = requiredConfig("jilatax.splash.backgroundColor")
37
+
38
+ val signingFile = rootProject.file("keystore.properties")
39
+ val signingProperties = Properties()
40
+ val signingKeys = listOf("storeFile", "storePassword", "keyAlias", "keyPassword")
41
+ val signingConfigured = signingFile.isFile
42
+ if (signingConfigured) {
43
+ signingFile.reader(Charsets.UTF_8).use(signingProperties::load)
44
+ check(signingKeys.all { !signingProperties.getProperty(it).isNullOrBlank() }) {
45
+ "android/keystore.properties exists but is incomplete."
46
+ }
47
+ }
48
+
49
+ android {
50
+ namespace = appPackage
51
+ compileSdk = 35
52
+
53
+ defaultConfig {
54
+ applicationId = appPackage
55
+ minSdk = 24
56
+ targetSdk = 35
57
+ versionCode = appVersionCode
58
+ versionName = appVersion
59
+ manifestPlaceholders["jilataxOrientation"] = screenOrientation
60
+ manifestPlaceholders["jilataxPredictiveBack"] = predictiveBack.toString()
61
+ manifestPlaceholders["jilataxScheme"] = appScheme
62
+ resValue("string", "jilatax_app_name", appName)
63
+ resValue("color", "jilatax_icon_background", splashBackground)
64
+ resValue("color", "jilatax_splash_background", splashBackground)
65
+ }
66
+
67
+ sourceSets {
68
+ getByName("main").assets.srcDir(file("../../.jilatax/android-assets"))
69
+ }
70
+
71
+ signingConfigs {
72
+ if (signingConfigured) {
73
+ create("release") {
74
+ storeFile = rootProject.file(signingProperties.getProperty("storeFile"))
75
+ storePassword = signingProperties.getProperty("storePassword")
76
+ keyAlias = signingProperties.getProperty("keyAlias")
77
+ keyPassword = signingProperties.getProperty("keyPassword")
78
+ }
79
+ }
80
+ }
81
+
82
+ buildTypes {
83
+ debug {
84
+ isDebuggable = true
85
+ }
86
+ release {
87
+ isMinifyEnabled = false
88
+ if (signingConfigured) {
89
+ signingConfig = signingConfigs.getByName("release")
90
+ }
91
+ proguardFiles(
92
+ getDefaultProguardFile("proguard-android-optimize.txt"),
93
+ "proguard-rules.pro",
94
+ )
95
+ }
96
+ }
97
+
98
+ compileOptions {
99
+ sourceCompatibility = JavaVersion.VERSION_11
100
+ targetCompatibility = JavaVersion.VERSION_11
101
+ }
102
+ }
103
+
104
+ kotlin {
105
+ compilerOptions {
106
+ jvmTarget.set(JvmTarget.JVM_11)
107
+ }
108
+ }
109
+
110
+ dependencies {
111
+ implementation(project(":jilatax"))
112
+ }
@@ -0,0 +1 @@
1
+ # Jilatax release minification is disabled during the Android foundation phase.
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest
3
+ xmlns:android="http://schemas.android.com/apk/res/android"
4
+ xmlns:tools="http://schemas.android.com/tools">
5
+ <application
6
+ android:usesCleartextTraffic="true"
7
+ tools:replace="android:usesCleartextTraffic" />
8
+ </manifest>