create-mithril-lynx 0.0.1 → 0.0.2

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 (27) hide show
  1. package/README.md +34 -12
  2. package/package.json +3 -5
  3. package/src/index.js +30 -6
  4. package/templates/basic-activity/common/src/style.css +59 -0
  5. package/templates/basic-activity/js/src/index.js +43 -0
  6. package/templates/basic-activity/ts/src/index.ts +48 -0
  7. package/templates/blank/common/src/style.css +16 -0
  8. package/templates/blank/js/src/index.js +11 -0
  9. package/templates/blank/ts/src/index.ts +11 -0
  10. /package/{template-js → templates/hello-world/js}/src/index.js +0 -0
  11. /package/{template-common → templates/_shared/common}/README.md +0 -0
  12. /package/{template-common → templates/_shared/common}/gitignore +0 -0
  13. /package/{template-js → templates/_shared/js}/lynx.config.js +0 -0
  14. /package/{template-js → templates/_shared/js}/package.json +0 -0
  15. /package/{template-js → templates/_shared/js}/src/main-thread.js +0 -0
  16. /package/{template-ts → templates/_shared/ts}/lynx.config.ts +0 -0
  17. /package/{template-ts → templates/_shared/ts}/package.json +0 -0
  18. /package/{template-ts → templates/_shared/ts}/src/main-thread.ts +0 -0
  19. /package/{template-ts → templates/_shared/ts}/src/rspeedy-env.d.ts +0 -0
  20. /package/{template-ts → templates/_shared/ts}/src/tsconfig.json +0 -0
  21. /package/{template-ts → templates/_shared/ts}/tsconfig.json +0 -0
  22. /package/{template-ts → templates/_shared/ts}/tsconfig.node.json +0 -0
  23. /package/{template-common → templates/hello-world/common}/src/assets/arrow.png +0 -0
  24. /package/{template-common → templates/hello-world/common}/src/assets/lynx-logo.png +0 -0
  25. /package/{template-common → templates/hello-world/common}/src/assets/mithril-logo.png +0 -0
  26. /package/{template-common → templates/hello-world/common}/src/style.css +0 -0
  27. /package/{template-ts → templates/hello-world/ts}/src/index.ts +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # create-mithril-lynx
2
2
 
3
- Scaffolds a new [`mithril-lynx`](https://github.com/carlos-sweb/mithril-lynx) app the Mithril.js analog of Lynx's official React hello-world template ([`lynx-examples/examples/hello-world`](https://github.com/lynx-family/lynx-examples/tree/main/examples/hello-world)). Same layout, same "tap the logo" interaction, running through Mithril hyperscript instead of JSX.
3
+ Scaffolds a new [`mithril-lynx`](https://github.com/carlos-sweb/mithril-lynx) app. Three templates, matching the "Hello World" / "Blank" / "Basic Activity" naming most native app scaffolds already use, each available in TypeScript or JavaScript.
4
4
 
5
5
  ## Usage
6
6
 
@@ -8,35 +8,57 @@ Scaffolds a new [`mithril-lynx`](https://github.com/carlos-sweb/mithril-lynx) ap
8
8
  npm create mithril-lynx@latest
9
9
  ```
10
10
 
11
- Prompts for a project name and a variant (TypeScript or JavaScript), scaffolds it, and offers to install dependencies for you.
11
+ Prompts for a project name, a template, and a variant (TypeScript or JavaScript), scaffolds it, and offers to install dependencies for you.
12
12
 
13
13
  ### Non-interactive
14
14
 
15
15
  ```bash
16
- npx create-mithril-lynx my-app --ts
17
- npx create-mithril-lynx my-app --js --no-install
16
+ npx create-mithril-lynx my-app --hello-world --ts
17
+ npx create-mithril-lynx my-app --basic-activity --js --no-install
18
18
  ```
19
19
 
20
- ## What it scaffolds
20
+ ## Templates
21
+
22
+ | Template | What it is |
23
+ |---|---|
24
+ | **Hello World** (recommended) | The Mithril.js analog of Lynx's official React hello-world ([`lynx-examples/examples/hello-world`](https://github.com/lynx-family/lynx-examples/tree/main/examples/hello-world)) — same layout, same "tap the logo" interaction, running through Mithril hyperscript instead of JSX. |
25
+ | **Blank** | A single centered line of text. Nothing else — the starting point when you don't want any of Hello World's styling/assets in your way. |
26
+ | **Basic Activity** | Two screens (Main → Details) wired with [`mithril-lynx/navigation`](https://github.com/carlos-sweb/mithril-lynx#navigation) — a stack-based, in-memory navigator (deliberately not `m.route`; see that package's README for why Lynx pages can't use URL-based routing). Includes a reusable app-bar-with-back-button pattern. Confirmed working end to end on a real device: `push()` with data, the back button, and `pop()` all round-trip correctly. |
27
+
28
+ ## Variants
21
29
 
22
30
  | Variant | Adds over the base template |
23
31
  |---|---|
24
32
  | **TypeScript** (recommended) | `.ts` source files, `tsconfig.json` (project references, matching `@lynx-js/rspeedy`'s own generated config), `@rsbuild/plugin-type-check`, `@types/mithril` for real editor autocomplete on `m()` calls. |
25
33
  | **JavaScript** | Same app, plain `.js` with ES module `import`/`export` — no type-checking, no `tsconfig.json`. |
26
34
 
27
- Both variants share `template-common/`: the logo/arrow image assets, `style.css`, `.gitignore`, and the project's own `README.md`.
28
-
29
35
  ## Package structure
30
36
 
31
37
  ```
32
38
  create-mithril-lynx/
33
- src/index.js the CLI itself
34
- template-common/ shared across both variants
35
- template-js/ JavaScript-only files
36
- template-ts/ TypeScript-only files
39
+ src/index.js the CLI itself
40
+ templates/
41
+ _shared/
42
+ common/ gitignore, project README — shared by every template
43
+ js/ lynx.config.js, package.json, main-thread.js — shared by every JS variant
44
+ ts/ lynx.config.ts, tsconfig*, package.json, main-thread.ts — shared by every TS variant
45
+ hello-world/
46
+ common/src/ logo/arrow assets, style.css
47
+ js/src/index.js
48
+ ts/src/index.ts
49
+ blank/
50
+ common/src/style.css
51
+ js/src/index.js
52
+ ts/src/index.ts
53
+ basic-activity/
54
+ common/src/style.css
55
+ js/src/index.js
56
+ ts/src/index.ts
37
57
  ```
38
58
 
39
- `src/index.js` copies `template-common/` then `template-<variant>/` into the target directory, renames `gitignore` to `.gitignore` (npm doesn't publish dotfiles reliably otherwise), and replaces `{{PROJECT_NAME}}`/`{{MITHRIL_LYNX_VERSION}}` placeholders in `package.json` and `README.md`.
59
+ `src/index.js` copies, in order, `templates/_shared/common` → `templates/_shared/<variant>` → `templates/<template>/common` `templates/<template>/<variant>` into the target directory (later copies never need to overwrite earlier ones — each layer contributes different files), renames `gitignore` to `.gitignore` (npm doesn't publish dotfiles reliably otherwise), and replaces `{{PROJECT_NAME}}`/`{{MITHRIL_LYNX_VERSION}}` placeholders in `package.json` and `README.md`.
60
+
61
+ Every template's `src/index.{js,ts}` exports the same shape (`{ root }`), so `_shared/{js,ts}/src/main-thread.{js,ts}` — the actual `__RenderPage` wiring — is 100% shared and never duplicated per template.
40
62
 
41
63
  ## License
42
64
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-mithril-lynx",
3
- "version": "0.0.1",
4
- "description": "Scaffolds a new mithril-lynx app — the Mithril.js analog of Lynx's official React hello-world template.",
3
+ "version": "0.0.2",
4
+ "description": "Scaffolds a new mithril-lynx app — Hello World, Blank, or Basic Activity (with navigation), in JavaScript or TypeScript.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -17,9 +17,7 @@
17
17
  },
18
18
  "files": [
19
19
  "src",
20
- "template-common",
21
- "template-js",
22
- "template-ts"
20
+ "templates"
23
21
  ],
24
22
  "engines": {
25
23
  "node": "^20.19.0 || >=22.12.0"
package/src/index.js CHANGED
@@ -10,7 +10,16 @@ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
10
10
  const packageRoot = path.join(scriptDir, "..");
11
11
  const cwd = process.cwd();
12
12
 
13
- const MITHRIL_LYNX_VERSION = "0.0.1";
13
+ // 0.0.2+ "basic-activity" needs mithril-lynx/navigation, added after 0.0.1
14
+ // was published.
15
+ const MITHRIL_LYNX_VERSION = "0.0.2";
16
+
17
+ const TEMPLATES = [
18
+ { value: "hello-world", label: "Hello World", hint: "recommended" },
19
+ { value: "blank", label: "Blank" },
20
+ { value: "basic-activity", label: "Basic Activity", hint: "multi-screen navigation" },
21
+ ];
22
+ const TEMPLATE_VALUES = TEMPLATES.map((t) => t.value);
14
23
 
15
24
  function isValidPackageName(name) {
16
25
  return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
@@ -40,13 +49,14 @@ function targetDirHasConflict(value) {
40
49
 
41
50
  async function main() {
42
51
  // Non-interactive escape hatch for scripting/CI:
43
- // create-mithril-lynx my-app --ts
44
- // create-mithril-lynx my-app --js --no-install
52
+ // create-mithril-lynx my-app --ts --hello-world
53
+ // create-mithril-lynx my-app --js --basic-activity --no-install
45
54
  const args = process.argv.slice(2);
46
55
  const positional = args.find((a) => !a.startsWith("-"));
47
56
  const variantFlag = args.includes("--ts") ? "ts" : args.includes("--js") ? "js" : undefined;
57
+ const templateFlag = TEMPLATE_VALUES.find((t) => args.includes(`--${t}`));
48
58
  const noInstall = args.includes("--no-install");
49
- const nonInteractive = positional != null && variantFlag != null;
59
+ const nonInteractive = positional != null && variantFlag != null && templateFlag != null;
50
60
 
51
61
  intro("create-mithril-lynx");
52
62
 
@@ -73,6 +83,15 @@ async function main() {
73
83
  ? rawName
74
84
  : rawName.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-~]/g, "-");
75
85
 
86
+ let template = templateFlag;
87
+ if (template == null) {
88
+ template = await select({
89
+ message: "Select a template",
90
+ options: TEMPLATES,
91
+ });
92
+ if (isCancel(template)) return bail();
93
+ }
94
+
76
95
  let variant = variantFlag;
77
96
  if (variant == null) {
78
97
  variant = await select({
@@ -88,8 +107,13 @@ async function main() {
88
107
  const targetDir = path.join(cwd, rawName);
89
108
  fs.mkdirSync(targetDir, { recursive: true });
90
109
 
91
- copyDir(path.join(packageRoot, "template-common"), targetDir);
92
- copyDir(path.join(packageRoot, `template-${variant}`), targetDir);
110
+ // Layered copy: shared build plumbing (gitignore/README, then
111
+ // lynx.config/tsconfig/main-thread for the chosen variant), then the
112
+ // chosen template's own app content (style.css/assets, then src/index).
113
+ copyDir(path.join(packageRoot, "templates/_shared/common"), targetDir);
114
+ copyDir(path.join(packageRoot, "templates/_shared", variant), targetDir);
115
+ copyDir(path.join(packageRoot, "templates", template, "common"), targetDir);
116
+ copyDir(path.join(packageRoot, "templates", template, variant), targetDir);
93
117
 
94
118
  const gitignorePath = path.join(targetDir, "gitignore");
95
119
  if (fs.existsSync(gitignorePath)) {
@@ -0,0 +1,59 @@
1
+ :root {
2
+ background-color: #fff;
3
+ }
4
+
5
+ .Page {
6
+ min-height: 100vh;
7
+ display: flex;
8
+ flex-direction: column;
9
+ }
10
+
11
+ .AppBar {
12
+ height: 56px;
13
+ display: flex;
14
+ align-items: center;
15
+ padding: 0 16px;
16
+ background-color: #6200ee;
17
+ }
18
+
19
+ .AppBar-back {
20
+ font-size: 22px;
21
+ font-weight: 700;
22
+ color: #fff;
23
+ margin-right: 16px;
24
+ padding: 4px 8px;
25
+ }
26
+
27
+ .AppBar-title {
28
+ font-size: 20px;
29
+ font-weight: 600;
30
+ color: #fff;
31
+ }
32
+
33
+ .Content {
34
+ flex: 1;
35
+ display: flex;
36
+ flex-direction: column;
37
+ align-items: center;
38
+ justify-content: center;
39
+ padding: 24px;
40
+ }
41
+
42
+ .Description {
43
+ font-size: 16px;
44
+ color: #333;
45
+ text-align: center;
46
+ margin-bottom: 24px;
47
+ }
48
+
49
+ .Button {
50
+ padding: 12px 24px;
51
+ border-radius: 8px;
52
+ background-color: #6200ee;
53
+ }
54
+
55
+ .Button-label {
56
+ color: #fff;
57
+ font-size: 16px;
58
+ font-weight: 600;
59
+ }
@@ -0,0 +1,43 @@
1
+ import m from "mithril";
2
+ import { createNavigator } from "mithril-lynx/navigation";
3
+
4
+ // A reusable app bar: a title, plus a back affordance when `onBack` is
5
+ // given. No m.route involved anywhere in this file — see
6
+ // mithril-lynx/navigation's own docs for why (Lynx pages have no URL to
7
+ // route to). Navigation here is a plain in-memory stack.
8
+ function AppBar(title, onBack) {
9
+ return m("view", { class: "AppBar" }, [
10
+ onBack != null ? m("text", { class: "AppBar-back", ontap: onBack }, "←") : null,
11
+ m("text", { class: "AppBar-title" }, title),
12
+ ]);
13
+ }
14
+
15
+ const MainScreen = {
16
+ view(vnode) {
17
+ return m("view", { class: "Page" }, [
18
+ AppBar("Basic Activity"),
19
+ m("view", { class: "Content" }, [
20
+ m("text", { class: "Description" }, "This is the main screen."),
21
+ m("view", {
22
+ class: "Button",
23
+ ontap: () => vnode.attrs.nav.push(DetailsScreen, { message: "Hello from MainScreen!" }),
24
+ }, [m("text", { class: "Button-label" }, "Next")]),
25
+ ]),
26
+ ]);
27
+ },
28
+ };
29
+
30
+ const DetailsScreen = {
31
+ view(vnode) {
32
+ return m("view", { class: "Page" }, [
33
+ AppBar("Details", () => vnode.attrs.nav.pop()),
34
+ m("view", { class: "Content" }, [
35
+ m("text", { class: "Description" }, vnode.attrs.message),
36
+ ]),
37
+ ]);
38
+ },
39
+ };
40
+
41
+ const nav = createNavigator({ initial: MainScreen });
42
+
43
+ export default { root: m(nav.Navigator) };
@@ -0,0 +1,48 @@
1
+ import m from "mithril";
2
+ import { createNavigator, type Nav } from "mithril-lynx/navigation";
3
+
4
+ interface DetailsAttrs {
5
+ nav: Nav;
6
+ message: string;
7
+ }
8
+
9
+ // A reusable app bar: a title, plus a back affordance when `onBack` is
10
+ // given. No m.route involved anywhere in this file — see
11
+ // mithril-lynx/navigation's own docs for why (Lynx pages have no URL to
12
+ // route to). Navigation here is a plain in-memory stack.
13
+ function AppBar(title: string, onBack?: () => void) {
14
+ return m("view", { class: "AppBar" }, [
15
+ onBack != null ? m("text", { class: "AppBar-back", ontap: onBack }, "←") : null,
16
+ m("text", { class: "AppBar-title" }, title),
17
+ ]);
18
+ }
19
+
20
+ const MainScreen: m.Component<{ nav: Nav }> = {
21
+ view(vnode) {
22
+ return m("view", { class: "Page" }, [
23
+ AppBar("Basic Activity"),
24
+ m("view", { class: "Content" }, [
25
+ m("text", { class: "Description" }, "This is the main screen."),
26
+ m("view", {
27
+ class: "Button",
28
+ ontap: () => vnode.attrs.nav.push(DetailsScreen, { message: "Hello from MainScreen!" }),
29
+ }, [m("text", { class: "Button-label" }, "Next")]),
30
+ ]),
31
+ ]);
32
+ },
33
+ };
34
+
35
+ const DetailsScreen: m.Component<DetailsAttrs> = {
36
+ view(vnode) {
37
+ return m("view", { class: "Page" }, [
38
+ AppBar("Details", () => vnode.attrs.nav.pop()),
39
+ m("view", { class: "Content" }, [
40
+ m("text", { class: "Description" }, vnode.attrs.message),
41
+ ]),
42
+ ]);
43
+ },
44
+ };
45
+
46
+ const nav = createNavigator({ initial: MainScreen });
47
+
48
+ export default { root: m(nav.Navigator) };
@@ -0,0 +1,16 @@
1
+ :root {
2
+ background-color: #fff;
3
+ }
4
+
5
+ .Page {
6
+ min-height: 100vh;
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: center;
10
+ justify-content: center;
11
+ }
12
+
13
+ .Title {
14
+ font-size: 24px;
15
+ font-weight: 600;
16
+ }
@@ -0,0 +1,11 @@
1
+ import m from "mithril";
2
+
3
+ const App = {
4
+ view() {
5
+ return m("view", { class: "Page" }, [
6
+ m("text", { class: "Title" }, "Hello, mithril-lynx!"),
7
+ ]);
8
+ },
9
+ };
10
+
11
+ export default { App, root: m(App) };
@@ -0,0 +1,11 @@
1
+ import m from "mithril";
2
+
3
+ const App: m.Component = {
4
+ view() {
5
+ return m("view", { class: "Page" }, [
6
+ m("text", { class: "Title" }, "Hello, mithril-lynx!"),
7
+ ]);
8
+ },
9
+ };
10
+
11
+ export default { App, root: m(App) };