@inglorious/create-app 1.3.0 → 1.4.1

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.
@@ -12,14 +12,6 @@ export const extension = (app) => (app.isTypeScript ? "tsx" : "jsx")
12
12
  const EXPECT_ERROR =
13
13
  "@ts-expect-error - Inglorious components are objects, not functions"
14
14
 
15
- // The comment line before a capitalized tag in .tsx: `{/* */}` among JSX
16
- // children, `//` inside code.
17
- function expectError(app, indent, isChild = true) {
18
- if (!app.isTypeScript) return ""
19
- const comment = isChild ? `{/* ${EXPECT_ERROR} */}` : `// ${EXPECT_ERROR}`
20
- return `${indent}${comment}\n`
21
- }
22
-
23
15
  export function appSource(app, { pages, navIcons }) {
24
16
  const imports = []
25
17
  if (navIcons) {
@@ -69,6 +61,105 @@ ${shell}
69
61
  `
70
62
  }
71
63
 
64
+ export function homeSource(app, { showcase }) {
65
+ const imports = ['import { Message } from "../types/message"']
66
+ for (const icon of showcase) {
67
+ imports.push(`import ${icon.name} from "${icon.from}"`)
68
+ }
69
+ if (showcase.length) {
70
+ imports.push('import { Icon } from "@inglorious/ui/icon"')
71
+ }
72
+ if (app.i18n) imports.push('import { t } from "../i18n"')
73
+
74
+ const text = (key, fallback) => (app.i18n ? `{t("${key}")}` : fallback)
75
+ const icons = showcase.length
76
+ ? `
77
+ <p className="app-icons">
78
+ ${showcase
79
+ .map(
80
+ (icon) =>
81
+ `${expectError(app, " ")} <Icon icon={${icon.name}} size="lg" label="${icon.label}" />`,
82
+ )
83
+ .join("\n")}
84
+ </p>`
85
+ : ""
86
+
87
+ return `${importBlock(imports)}
88
+
89
+ export const Home = {
90
+ render() {
91
+ return (
92
+ <>
93
+ <h1>${text("home.title", "Welcome")}</h1>
94
+ <p>
95
+ ${expectError(app, " ")} <Message entityId="message1" />, <Message entityId="message2" />,{" "}
96
+ ${expectError(app, " ")} <Message entityId="message3" />!
97
+ </p>
98
+ <p>${text("home.hint", "Click a greeting to shout it.")}</p>${icons}
99
+ </>
100
+ )
101
+ },
102
+ }
103
+ `
104
+ }
105
+
106
+ export function aboutSource(app) {
107
+ const head = app.i18n ? 'import { t } from "../i18n"\n\n' : ""
108
+ const text = (key, fallback) => (app.i18n ? `{t("${key}")}` : fallback)
109
+
110
+ return `${head}export const About = {
111
+ render() {
112
+ return (
113
+ <>
114
+ <h1>${text("about.title", "About")}</h1>
115
+ <p>${text("about.body", "A page of its own, at its own address.")}</p>
116
+ </>
117
+ )
118
+ },
119
+ }
120
+ `
121
+ }
122
+
123
+ export function messageSource(app) {
124
+ const imports = []
125
+ if (app.isTypeScript) {
126
+ imports.push('import type { MessageType } from "../../types"')
127
+ }
128
+ if (app.i18n) imports.push('import { t } from "../i18n"')
129
+ const greeting = app.i18n ? '{t("message.hello", { who })}' : "Hello {who}"
130
+ const annotation = app.isTypeScript ? ": MessageType" : ""
131
+
132
+ const span = app.i18n
133
+ ? ` <span onClick={() => api.notify(\`#\${entity.id}:click\`)}>
134
+ ${greeting}
135
+ </span>`
136
+ : ` <span onClick={() => api.notify(\`#\${entity.id}:click\`)}>${greeting}</span>`
137
+
138
+ const head = imports.length ? `${importBlock(imports)}\n\n` : ""
139
+
140
+ return `${head}export const Message${annotation} = {
141
+ click(entity) {
142
+ entity.isUpperCase = !entity.isUpperCase
143
+ },
144
+
145
+ render(entity, api) {
146
+ const who = entity.isUpperCase ? entity.who.toUpperCase() : entity.who
147
+ return (
148
+ ${span}
149
+ )
150
+ },
151
+ }
152
+ `
153
+ }
154
+
155
+ // The comment line before a capitalized tag in .tsx: `{/* */}` among JSX
156
+ // children, `//` inside code.
157
+ function expectError(app, indent, isChild = true) {
158
+ if (!app.isTypeScript) return ""
159
+ const comment = isChild ? `{/* ${EXPECT_ERROR} */}` : `// ${EXPECT_ERROR}`
160
+ return `${indent}${comment}\n`
161
+ }
162
+
72
163
  // A callback's parameter. The components' props are not typed in .tsx, so
73
164
  // TypeScript cannot infer it.
74
165
  function typed(app, name, type) {
@@ -185,96 +276,3 @@ ${nav.join("\n")}
185
276
  </header>
186
277
  <main>{${content}}</main>`
187
278
  }
188
-
189
- export function homeSource(app, { showcase }) {
190
- const imports = ['import { Message } from "../types/message"']
191
- for (const icon of showcase) {
192
- imports.push(`import ${icon.name} from "${icon.from}"`)
193
- }
194
- if (showcase.length) {
195
- imports.push('import { Icon } from "@inglorious/ui/icon"')
196
- }
197
- if (app.i18n) imports.push('import { t } from "../i18n"')
198
-
199
- const text = (key, fallback) => (app.i18n ? `{t("${key}")}` : fallback)
200
- const icons = showcase.length
201
- ? `
202
- <p className="app-icons">
203
- ${showcase
204
- .map(
205
- (icon) =>
206
- `${expectError(app, " ")} <Icon icon={${icon.name}} size="lg" label="${icon.label}" />`,
207
- )
208
- .join("\n")}
209
- </p>`
210
- : ""
211
-
212
- return `${importBlock(imports)}
213
-
214
- export const Home = {
215
- render() {
216
- return (
217
- <>
218
- <h1>${text("home.title", "Welcome")}</h1>
219
- <p>
220
- ${expectError(app, " ")} <Message entityId="message1" />, <Message entityId="message2" />,{" "}
221
- ${expectError(app, " ")} <Message entityId="message3" />!
222
- </p>
223
- <p>${text("home.hint", "Click a greeting to shout it.")}</p>${icons}
224
- </>
225
- )
226
- },
227
- }
228
- `
229
- }
230
-
231
- export function aboutSource(app) {
232
- const head = app.i18n ? 'import { t } from "../i18n"\n\n' : ""
233
- const text = (key, fallback) => (app.i18n ? `{t("${key}")}` : fallback)
234
-
235
- return `${head}export const About = {
236
- render() {
237
- return (
238
- <>
239
- <h1>${text("about.title", "About")}</h1>
240
- <p>${text("about.body", "A page of its own, at its own address.")}</p>
241
- </>
242
- )
243
- },
244
- }
245
- `
246
- }
247
-
248
- export function messageSource(app) {
249
- const imports = []
250
- if (app.isTypeScript) {
251
- imports.push('import type { MessageType } from "../../types"')
252
- }
253
- if (app.i18n) imports.push('import { t } from "../i18n"')
254
- const greeting = app.i18n ? '{t("message.hello", { who })}' : "Hello {who}"
255
- const declaration = app.isTypeScript
256
- ? "export const Message: MessageType = {"
257
- : "export const Message = {"
258
-
259
- const span = app.i18n
260
- ? ` <span onClick={() => api.notify(\`#\${entity.id}:click\`)}>
261
- ${greeting}
262
- </span>`
263
- : ` <span onClick={() => api.notify(\`#\${entity.id}:click\`)}>${greeting}</span>`
264
-
265
- const head = imports.length ? `${importBlock(imports)}\n\n` : ""
266
-
267
- return `${head}${declaration}
268
- click(entity) {
269
- entity.isUpperCase = !entity.isUpperCase
270
- },
271
-
272
- render(entity, api) {
273
- const who = entity.isUpperCase ? entity.who.toUpperCase() : entity.who
274
- return (
275
- ${span}
276
- )
277
- },
278
- }
279
- `
280
- }
@@ -64,70 +64,6 @@ export function renderLocals(app) {
64
64
  return locals.length ? `${locals.join("\n")}\n\n` : ""
65
65
  }
66
66
 
67
- function uiShell(app, { label, pages, content }) {
68
- const nav = []
69
- if (app.links) {
70
- nav.push(
71
- ...pages.map(
72
- (page) =>
73
- `Link.render({ href: "${page.path}", children: ${label(page.key, page.text)} })`,
74
- ),
75
- )
76
- }
77
- if (app.i18n) {
78
- nav.push(`Select.render({
79
- value: i18n.locale,
80
- options: i18n.languages,
81
- size: "sm",
82
- ariaLabel: t("language"),
83
- onChange: (locale) => api.notify("#i18n:localeChange", locale),
84
- })`)
85
- }
86
-
87
- const trailing = nav.length
88
- ? `
89
- trailing: html\`<nav class="app-nav">
90
- \${[
91
- ${nav.join(",\n ")},
92
- ]}
93
- </nav>\`,`
94
- : ""
95
-
96
- const header = ` <header${app.mobile ? ' class="pt-safe"' : ""}>
97
- \${AppBar.render({
98
- title: "${app.projectName}",${trailing}
99
- })}
100
- </header>
101
- <main>
102
- \${Container.render({ maxWidth: "md", children: ${content} })}
103
- </main>`
104
-
105
- if (!app.tabs) return header
106
-
107
- const actions = pages.map(
108
- (page) => ` {
109
- value: "${page.path}",
110
- label: ${label(page.key, page.text)},${
111
- page.icon
112
- ? `
113
- icon: Icon.render({ icon: ${page.icon.name}, size: "lg" }),`
114
- : ""
115
- }
116
- },`,
117
- )
118
-
119
- return `${header}
120
- <footer class="pb-safe">
121
- \${BottomNavigation.render({
122
- value: router.path,
123
- actions: [
124
- ${actions.join("\n")}
125
- ],
126
- onChange: (path) => api.notify("#router:navigate", path),
127
- })}
128
- </footer>`
129
- }
130
-
131
67
  // The native language picker's change handler, laid out as Prettier would at
132
68
  // `indent`. TypeScript has to be told what the event's target is.
133
69
  export function selectChange(app, indent) {
@@ -142,42 +78,6 @@ ${indent} (event.target as HTMLSelectElement).value,
142
78
  ${indent} )`
143
79
  }
144
80
 
145
- function plainShell(app, { label, pages, content }) {
146
- const nav = app.routing
147
- ? pages.map(
148
- (page) =>
149
- ` <a href="${page.path}">\${${label(page.key, page.text)}}</a>`,
150
- )
151
- : []
152
- if (app.i18n) {
153
- nav.push(` <select
154
- aria-label=\${t("language")}
155
- @change=\${${selectChange(app, " ")}}
156
- >
157
- \${i18n.languages${app.isTypeScript ? "?." : "."}map(
158
- (language) =>
159
- html\`<option
160
- value=\${language.value}
161
- ?selected=\${language.value === i18n.locale}
162
- >
163
- \${language.label}
164
- </option>\`,
165
- )}
166
- </select>`)
167
- }
168
- const navBlock = nav.length
169
- ? `
170
- <nav class="app-nav">
171
- ${nav.join("\n")}
172
- </nav>`
173
- : ""
174
-
175
- return ` <header>
176
- <strong>${app.projectName}</strong>${navBlock}
177
- </header>
178
- <main>\${${content}}</main>`
179
- }
180
-
181
81
  export function homeSource(app, { showcase }) {
182
82
  const imports = ['import { html } from "@inglorious/web"']
183
83
  for (const icon of showcase) {
@@ -187,7 +87,7 @@ export function homeSource(app, { showcase }) {
187
87
  imports.push('import { Icon } from "@inglorious/ui/icon"')
188
88
  }
189
89
  if (app.isTypeScript) {
190
- imports.push('import type { AppApi, HomeEntity } from "../../types"')
90
+ imports.push('import type { HomeType } from "../../types"')
191
91
  }
192
92
  if (app.i18n) imports.push('import { t } from "../i18n"')
193
93
 
@@ -207,14 +107,12 @@ ${showcase
207
107
  </p>`
208
108
  : ""
209
109
 
210
- const signature = app.isTypeScript
211
- ? "render(entity: HomeEntity, api: AppApi)"
212
- : "render(entity, api)"
110
+ const annotation = app.isTypeScript ? ": HomeType" : ""
213
111
 
214
112
  return `${importBlock(imports)}
215
113
 
216
- export const Home = {
217
- ${signature} {
114
+ export const Home${annotation} = {
115
+ render(entity, api) {
218
116
  return html\`
219
117
  <h1>${text("home.title", "Welcome")}</h1>
220
118
  <p>
@@ -253,13 +151,11 @@ export function messageSource(app) {
253
151
  }
254
152
  if (app.i18n) imports.push('import { t } from "../i18n"')
255
153
  const greeting = app.i18n ? '${t("message.hello", { who })}' : "Hello ${who}"
256
- const declaration = app.isTypeScript
257
- ? "export const Message: MessageType = {"
258
- : "export const Message = {"
154
+ const annotation = app.isTypeScript ? ": MessageType" : ""
259
155
 
260
156
  return `${importBlock(imports)}
261
157
 
262
- ${declaration}
158
+ export const Message${annotation} = {
263
159
  click(entity) {
264
160
  entity.isUpperCase = !entity.isUpperCase
265
161
  },
@@ -273,3 +169,103 @@ ${declaration}
273
169
  }
274
170
  `
275
171
  }
172
+
173
+ function uiShell(app, { label, pages, content }) {
174
+ const nav = []
175
+ if (app.links) {
176
+ nav.push(
177
+ ...pages.map(
178
+ (page) =>
179
+ `Link.render({ href: "${page.path}", children: ${label(page.key, page.text)} })`,
180
+ ),
181
+ )
182
+ }
183
+ if (app.i18n) {
184
+ nav.push(`Select.render({
185
+ value: i18n.locale,
186
+ options: i18n.languages,
187
+ size: "sm",
188
+ ariaLabel: t("language"),
189
+ onChange: (locale) => api.notify("#i18n:localeChange", locale),
190
+ })`)
191
+ }
192
+
193
+ const trailing = nav.length
194
+ ? `
195
+ trailing: html\`<nav class="app-nav">
196
+ \${[
197
+ ${nav.join(",\n ")},
198
+ ]}
199
+ </nav>\`,`
200
+ : ""
201
+
202
+ const header = ` <header${app.mobile ? ' class="pt-safe"' : ""}>
203
+ \${AppBar.render({
204
+ title: "${app.projectName}",${trailing}
205
+ })}
206
+ </header>
207
+ <main>
208
+ \${Container.render({ maxWidth: "md", children: ${content} })}
209
+ </main>`
210
+
211
+ if (!app.tabs) return header
212
+
213
+ const actions = pages.map(
214
+ (page) => ` {
215
+ value: "${page.path}",
216
+ label: ${label(page.key, page.text)},${
217
+ page.icon
218
+ ? `
219
+ icon: Icon.render({ icon: ${page.icon.name}, size: "lg" }),`
220
+ : ""
221
+ }
222
+ },`,
223
+ )
224
+
225
+ return `${header}
226
+ <footer class="pb-safe">
227
+ \${BottomNavigation.render({
228
+ value: router.path,
229
+ actions: [
230
+ ${actions.join("\n")}
231
+ ],
232
+ onChange: (path) => api.notify("#router:navigate", path),
233
+ })}
234
+ </footer>`
235
+ }
236
+
237
+ function plainShell(app, { label, pages, content }) {
238
+ const nav = app.routing
239
+ ? pages.map(
240
+ (page) =>
241
+ ` <a href="${page.path}">\${${label(page.key, page.text)}}</a>`,
242
+ )
243
+ : []
244
+ if (app.i18n) {
245
+ nav.push(` <select
246
+ aria-label=\${t("language")}
247
+ @change=\${${selectChange(app, " ")}}
248
+ >
249
+ \${i18n.languages${app.isTypeScript ? "?." : "."}map(
250
+ (language) =>
251
+ html\`<option
252
+ value=\${language.value}
253
+ ?selected=\${language.value === i18n.locale}
254
+ >
255
+ \${language.label}
256
+ </option>\`,
257
+ )}
258
+ </select>`)
259
+ }
260
+ const navBlock = nav.length
261
+ ? `
262
+ <nav class="app-nav">
263
+ ${nav.join("\n")}
264
+ </nav>`
265
+ : ""
266
+
267
+ return ` <header>
268
+ <strong>${app.projectName}</strong>${navBlock}
269
+ </header>
270
+ <main>\${${content}}</main>`
271
+ }
@@ -51,6 +51,77 @@ ${template}
51
51
  `
52
52
  }
53
53
 
54
+ export function homeSource(app, { showcase }) {
55
+ const imports = ['import { Message } from "../types/message.vue"']
56
+ for (const icon of showcase) {
57
+ imports.push(`import ${icon.name} from "${icon.from}"`)
58
+ }
59
+ if (showcase.length) {
60
+ imports.push('import { Icon } from "@inglorious/ui/icon"')
61
+ }
62
+ if (app.i18n) imports.push('import { t } from "../i18n"')
63
+
64
+ const text = (key, fallback) => (app.i18n ? `{{ t("${key}") }}` : fallback)
65
+ const icons = showcase.length
66
+ ? `
67
+ <p class="app-icons">
68
+ ${showcase
69
+ .map(
70
+ (icon) =>
71
+ ` <Icon :icon="${icon.name}" size="lg" label="${icon.label}" />`,
72
+ )
73
+ .join("\n")}
74
+ </p>`
75
+ : ""
76
+
77
+ return `${script(imports, [])}<template>
78
+ <h1>${text("home.title", "Welcome")}</h1>
79
+ <p>
80
+ <Message v-bind="message1" />, <Message v-bind="message2" />,
81
+ <Message v-bind="message3" />!
82
+ </p>
83
+ <p>${text("home.hint", "Click a greeting to shout it.")}</p>${icons}
84
+ </template>
85
+ `
86
+ }
87
+
88
+ export function aboutSource(app) {
89
+ const imports = app.i18n ? ['import { t } from "../i18n"'] : []
90
+ const text = (key, fallback) => (app.i18n ? `{{ t("${key}") }}` : fallback)
91
+ return `${script(imports, [])}<template>
92
+ <h1>${text("about.title", "About")}</h1>
93
+ <p>${text("about.body", "A page of its own, at its own address.")}</p>
94
+ </template>
95
+ `
96
+ }
97
+
98
+ export function messageSource(app) {
99
+ const imports = app.i18n ? ['import { t } from "../i18n"'] : []
100
+ const who = "entity.isUpperCase ? entity.who.toUpperCase() : entity.who"
101
+ const span = app.i18n
102
+ ? ` <span @click="click">{{
103
+ t("message.hello", {
104
+ who: ${who},
105
+ })
106
+ }}</span>`
107
+ : ` <span @click="click"
108
+ >Hello
109
+ {{ ${who} }}</span
110
+ >`
111
+ const scriptBody = [
112
+ ...(imports.length ? [importBlock(imports), ""] : []),
113
+ "const click = (entity) => (entity.isUpperCase = !entity.isUpperCase)",
114
+ ]
115
+ return `<script>
116
+ ${scriptBody.join("\n")}
117
+ </script>
118
+
119
+ <template>
120
+ ${span}
121
+ </template>
122
+ `
123
+ }
124
+
54
125
  function script(imports, locals) {
55
126
  if (!imports.length && !locals.length) return ""
56
127
  const parts = []
@@ -166,74 +237,3 @@ ${nav.join("\n")}
166
237
  </header>
167
238
  <main>{{ ${content} }}</main>`
168
239
  }
169
-
170
- export function homeSource(app, { showcase }) {
171
- const imports = ['import { Message } from "../types/message.vue"']
172
- for (const icon of showcase) {
173
- imports.push(`import ${icon.name} from "${icon.from}"`)
174
- }
175
- if (showcase.length) {
176
- imports.push('import { Icon } from "@inglorious/ui/icon"')
177
- }
178
- if (app.i18n) imports.push('import { t } from "../i18n"')
179
-
180
- const text = (key, fallback) => (app.i18n ? `{{ t("${key}") }}` : fallback)
181
- const icons = showcase.length
182
- ? `
183
- <p class="app-icons">
184
- ${showcase
185
- .map(
186
- (icon) =>
187
- ` <Icon :icon="${icon.name}" size="lg" label="${icon.label}" />`,
188
- )
189
- .join("\n")}
190
- </p>`
191
- : ""
192
-
193
- return `${script(imports, [])}<template>
194
- <h1>${text("home.title", "Welcome")}</h1>
195
- <p>
196
- <Message v-bind="message1" />, <Message v-bind="message2" />,
197
- <Message v-bind="message3" />!
198
- </p>
199
- <p>${text("home.hint", "Click a greeting to shout it.")}</p>${icons}
200
- </template>
201
- `
202
- }
203
-
204
- export function aboutSource(app) {
205
- const imports = app.i18n ? ['import { t } from "../i18n"'] : []
206
- const text = (key, fallback) => (app.i18n ? `{{ t("${key}") }}` : fallback)
207
- return `${script(imports, [])}<template>
208
- <h1>${text("about.title", "About")}</h1>
209
- <p>${text("about.body", "A page of its own, at its own address.")}</p>
210
- </template>
211
- `
212
- }
213
-
214
- export function messageSource(app) {
215
- const imports = app.i18n ? ['import { t } from "../i18n"'] : []
216
- const who = "entity.isUpperCase ? entity.who.toUpperCase() : entity.who"
217
- const span = app.i18n
218
- ? ` <span @click="click">{{
219
- t("message.hello", {
220
- who: ${who},
221
- })
222
- }}</span>`
223
- : ` <span @click="click"
224
- >Hello
225
- {{ ${who} }}</span
226
- >`
227
- const scriptBody = [
228
- ...(imports.length ? [importBlock(imports), ""] : []),
229
- "const click = (entity) => (entity.isUpperCase = !entity.isUpperCase)",
230
- ]
231
- return `<script>
232
- ${scriptBody.join("\n")}
233
- </script>
234
-
235
- <template>
236
- ${span}
237
- </template>
238
- `
239
- }
package/bin/lib/app.js CHANGED
@@ -227,9 +227,7 @@ main {
227
227
 
228
228
  function storeSource(app) {
229
229
  const imports = [
230
- app.isTypeScript
231
- ? 'import { createStore, type TypesConfig } from "@inglorious/store"'
232
- : 'import { createStore } from "@inglorious/store"',
230
+ 'import { createStore } from "@inglorious/store"',
233
231
  componentImport(app, "Home", "../pages/home"),
234
232
  componentImport(app, "Message", "../types/message"),
235
233
  'import { entities } from "./entities"',
@@ -250,14 +248,13 @@ function storeSource(app) {
250
248
  const types = ["Message", "Home"]
251
249
  if (app.routing) types.push("About", "Router")
252
250
  if (app.i18n) types.push("I18n")
253
- const cast = app.isTypeScript ? " as unknown as TypesConfig<AppEntity>" : ""
254
- const oneLine = ` types: { ${types.join(", ")} }${cast},`
251
+ const oneLine = ` types: { ${types.join(", ")} },`
255
252
  const typesValue =
256
253
  oneLine.length <= LINE_WIDTH
257
254
  ? oneLine
258
255
  : ` types: {
259
256
  ${types.map((type) => ` ${type},`).join("\n")}
260
- }${cast},`
257
+ },`
261
258
  const generics = app.isTypeScript ? "<AppEntity, AppState>" : ""
262
259
 
263
260
  return `${importBlock(imports)}
@@ -333,14 +330,13 @@ export const { I18n, t, ready } = createI18n({
333
330
  function typesSource(app) {
334
331
  const imports = [
335
332
  'import "./vite-end"',
336
- 'import type { BaseEntity, EntitiesState } from "@inglorious/store"',
333
+ 'import type {\n BaseEntity,\n EntitiesState,\n EntityType,\n} from "@inglorious/store"',
337
334
  'import type { Api, TemplateResult } from "@inglorious/web"',
338
335
  ]
339
336
  const entities = ["MessageEntity", "HomeEntity"]
340
337
  const declarations = [
341
338
  `export interface MessageEntity extends BaseEntity {
342
339
  type: "Message"
343
- id?: string
344
340
  who: string
345
341
  isUpperCase?: boolean
346
342
  }`,
@@ -362,7 +358,7 @@ function typesSource(app) {
362
358
  `export interface AboutEntity extends BaseEntity {
363
359
  type: "About"
364
360
  }`,
365
- `export interface AppRouterEntity extends BaseEntity, Omit<RouterEntity, "id"> {
361
+ `export interface AppRouterEntity extends RouterEntity {
366
362
  type: "Router"
367
363
  }`,
368
364
  )
@@ -373,7 +369,7 @@ function typesSource(app) {
373
369
  imports.push('import type { I18nEntity } from "@inglorious/i18n"')
374
370
  entities.push("AppI18nEntity")
375
371
  declarations.push(
376
- `export interface AppI18nEntity extends BaseEntity, Omit<I18nEntity, "id"> {
372
+ `export interface AppI18nEntity extends I18nEntity {
377
373
  type: "I18n"
378
374
  }`,
379
375
  )
@@ -394,9 +390,23 @@ ${state.join("\n")}
394
390
 
395
391
  export type AppApi = Api<AppEntity, AppState>
396
392
 
397
- export interface MessageType {
393
+ // Each type's events and their payloads. Declare types with \`type\`, not
394
+ // \`interface\`: an interface does not fit the store's \`types\`.
395
+ export type MessageType = {
398
396
  click: (entity: MessageEntity) => void
399
- render: (entity: MessageEntity, api: AppApi) => TemplateResult | null
397
+ render: (entity: MessageEntity, api: AppApi) => TemplateResult
398
+ }
399
+ ${
400
+ app.renderer === "lit-html"
401
+ ? `
402
+ export type HomeType = {
403
+ render: (entity: HomeEntity, api: AppApi) => TemplateResult
404
+ }
405
+ `
406
+ : ""
400
407
  }
408
+ // A shorthand for a type: \`{ ... } satisfies AppType<MessageEntity>\` infers
409
+ // its handlers' parameters, but neither lists its events nor types payloads.
410
+ export type AppType<TEntity extends AppEntity> = EntityType<TEntity, AppApi>
401
411
  `
402
412
  }
package/bin/lib/mobile.js CHANGED
@@ -34,21 +34,6 @@ export function addMobileSupport(targetDir, pkg, projectName) {
34
34
  appendMobileSetupToReadme(targetDir)
35
35
  }
36
36
 
37
- // A TypeScript template type-checks and lints its config files through
38
- // tsconfig.node.json, so a new one has to be listed there. Edited as text, so
39
- // the `include` line keeps the one-line layout Prettier gives it.
40
- function includeInNodeProject(targetDir, file) {
41
- const tsconfigPath = path.join(targetDir, "tsconfig.node.json")
42
- if (!fs.existsSync(tsconfigPath)) return
43
-
44
- const tsconfig = fs.readFileSync(tsconfigPath, "utf-8")
45
- if (tsconfig.includes(`"${file}"`)) return
46
- fs.writeFileSync(
47
- tsconfigPath,
48
- tsconfig.replace(/("include": \[[^\]]*)\]/, `$1, "${file}"]`),
49
- )
50
- }
51
-
52
37
  export function appendMobileSetupToReadme(targetDir) {
53
38
  const readmePath = path.join(targetDir, "README.md")
54
39
  if (!fs.existsSync(readmePath)) {
@@ -138,3 +123,18 @@ const config: CapacitorConfig = {
138
123
  export default config
139
124
  `
140
125
  }
126
+
127
+ // A TypeScript template type-checks and lints its config files through
128
+ // tsconfig.node.json, so a new one has to be listed there. Edited as text, so
129
+ // the `include` line keeps the one-line layout Prettier gives it.
130
+ function includeInNodeProject(targetDir, file) {
131
+ const tsconfigPath = path.join(targetDir, "tsconfig.node.json")
132
+ if (!fs.existsSync(tsconfigPath)) return
133
+
134
+ const tsconfig = fs.readFileSync(tsconfigPath, "utf-8")
135
+ if (tsconfig.includes(`"${file}"`)) return
136
+ fs.writeFileSync(
137
+ tsconfigPath,
138
+ tsconfig.replace(/("include": \[[^\]]*)\]/, `$1, "${file}"]`),
139
+ )
140
+ }
package/bin/lib/pwa.js CHANGED
@@ -93,7 +93,9 @@ export function writePwaRegistration(targetDir) {
93
93
 
94
94
  export function createPwaRegistrationSource(extension) {
95
95
  if (extension === ".ts") {
96
- return `/// <reference types="vite-plugin-pwa/client" />
96
+ return `// Only the framework-free helpers: "client" also loads React's and Preact's
97
+ // typings, whose global JSX would replace Inglorious's.
98
+ /// <reference types="vite-plugin-pwa/vanillajs" />
97
99
  import { isNative } from "@inglorious/web/mobile"
98
100
  import { registerSW } from "virtual:pwa-register"
99
101
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/create-app",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "A scaffolding tool to quickly create a new app with Inglorious Web.",
5
5
  "author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "license": "MIT",
@@ -37,7 +37,7 @@
37
37
  "prompts": "^2.4.2"
38
38
  },
39
39
  "devDependencies": {
40
- "@inglorious/eslint-config": "1.1.3",
40
+ "@inglorious/eslint-config": "1.1.4",
41
41
  "prettier": "^3.9.6"
42
42
  },
43
43
  "scripts": {
@@ -1,13 +1,13 @@
1
- import { Api, html } from "@inglorious/web"
1
+ import { html } from "@inglorious/web"
2
2
 
3
- import type { AboutEntity } from "../../types"
3
+ import type { AboutType } from "../../types"
4
4
 
5
- export const About = {
6
- click(entity: AboutEntity) {
5
+ export const About: AboutType = {
6
+ click(entity) {
7
7
  entity.name += "!"
8
8
  },
9
9
 
10
- render(entity: AboutEntity, api: Api) {
10
+ render(entity, api) {
11
11
  return html`
12
12
  <div>
13
13
  <h1>
@@ -1,9 +1,9 @@
1
- import { BaseEntity, EntitiesState } from "@inglorious/store"
1
+ import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"
2
+ import type { Api, TemplateResult } from "@inglorious/web"
2
3
 
3
4
  // Define your entity types
4
5
  export interface AboutEntity extends BaseEntity {
5
6
  type: "About"
6
- id?: string
7
7
  name: string
8
8
  }
9
9
 
@@ -15,9 +15,15 @@ export interface AppState extends EntitiesState<AppEntity> {
15
15
  about: AboutEntity
16
16
  }
17
17
 
18
- // Types configuration
19
- export interface AppTypes {
20
- About: {
21
- click: (entity: AboutEntity) => void
22
- }
18
+ export type AppApi = Api<AppEntity, AppState>
19
+
20
+ // Each type's events and their payloads. Declare types with `type`, not
21
+ // `interface`: an interface does not fit the store's `types`.
22
+ export type AboutType = {
23
+ click: (entity: AboutEntity) => void
24
+ render: (entity: AboutEntity, api: AppApi) => TemplateResult
23
25
  }
26
+
27
+ // A shorthand for a type: `{ ... } satisfies AppType<AboutEntity>` infers
28
+ // its handlers' parameters, but neither lists its events nor types payloads.
29
+ export type AppType<TEntity extends AppEntity> = EntityType<TEntity, AppApi>
@@ -1,4 +1,4 @@
1
- import { createStore, type TypesConfig } from "@inglorious/store"
1
+ import { createStore } from "@inglorious/store"
2
2
 
3
3
  import type { AppEntity, AppState } from "../../types"
4
4
  import { Message } from "../types/message"
@@ -6,7 +6,7 @@ import { entities } from "./entities"
6
6
  import { middlewares } from "./middlewares"
7
7
 
8
8
  export const store = createStore<AppEntity, AppState>({
9
- types: { Message } as unknown as TypesConfig<AppEntity>,
9
+ types: { Message },
10
10
  entities,
11
11
  middlewares,
12
12
  })
@@ -1,12 +1,11 @@
1
1
  import "./vite-end"
2
2
 
3
- import { BaseEntity, EntitiesState } from "@inglorious/store"
4
- import { Api, TemplateResult } from "@inglorious/web"
3
+ import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"
4
+ import type { Api, TemplateResult } from "@inglorious/web"
5
5
 
6
6
  // Define your entity types
7
7
  export interface MessageEntity extends BaseEntity {
8
8
  type: "Message"
9
- id?: string
10
9
  who: string
11
10
  isUpperCase?: boolean
12
11
  }
@@ -23,8 +22,13 @@ export interface AppState extends EntitiesState<AppEntity> {
23
22
 
24
23
  export type AppApi = Api<AppEntity, AppState>
25
24
 
26
- // Types configuration
27
- export interface MessageType {
25
+ // Each type's events and their payloads. Declare types with `type`, not
26
+ // `interface`: an interface does not fit the store's `types`.
27
+ export type MessageType = {
28
28
  click: (entity: MessageEntity) => void
29
- render: (entity: MessageEntity, api: AppApi) => TemplateResult | null
29
+ render: (entity: MessageEntity, api: AppApi) => TemplateResult
30
30
  }
31
+
32
+ // A shorthand for a type: `{ ... } satisfies AppType<MessageEntity>` infers
33
+ // its handlers' parameters, but neither lists its events nor types payloads.
34
+ export type AppType<TEntity extends AppEntity> = EntityType<TEntity, AppApi>
@@ -1,4 +1,4 @@
1
- import { createStore, type TypesConfig } from "@inglorious/store"
1
+ import { createStore } from "@inglorious/store"
2
2
 
3
3
  import type { AppEntity, AppState } from "../../types"
4
4
  import { Message } from "../types/message"
@@ -6,7 +6,7 @@ import { entities } from "./entities"
6
6
  import { middlewares } from "./middlewares"
7
7
 
8
8
  export const store = createStore<AppEntity, AppState>({
9
- types: { Message } as unknown as TypesConfig<AppEntity>,
9
+ types: { Message },
10
10
  entities,
11
11
  middlewares,
12
12
  })
@@ -1,12 +1,11 @@
1
1
  import "./vite-end"
2
2
 
3
- import { BaseEntity, EntitiesState } from "@inglorious/store"
4
- import { Api, TemplateResult } from "@inglorious/web"
3
+ import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"
4
+ import type { Api, TemplateResult } from "@inglorious/web"
5
5
 
6
6
  // Define your entity types
7
7
  export interface MessageEntity extends BaseEntity {
8
8
  type: "Message"
9
- id?: string
10
9
  who: string
11
10
  isUpperCase?: boolean
12
11
  }
@@ -23,8 +22,13 @@ export interface AppState extends EntitiesState<AppEntity> {
23
22
 
24
23
  export type AppApi = Api<AppEntity, AppState>
25
24
 
26
- // Types configuration
27
- export interface MessageType {
25
+ // Each type's events and their payloads. Declare types with `type`, not
26
+ // `interface`: an interface does not fit the store's `types`.
27
+ export type MessageType = {
28
28
  click: (entity: MessageEntity) => void
29
- render: (entity: MessageEntity, api: AppApi) => TemplateResult | null
29
+ render: (entity: MessageEntity, api: AppApi) => TemplateResult
30
30
  }
31
+
32
+ // A shorthand for a type: `{ ... } satisfies AppType<MessageEntity>` infers
33
+ // its handlers' parameters, but neither lists its events nor types payloads.
34
+ export type AppType<TEntity extends AppEntity> = EntityType<TEntity, AppApi>
@@ -1,4 +1,4 @@
1
- import { createStore, type TypesConfig } from "@inglorious/store"
1
+ import { createStore } from "@inglorious/store"
2
2
 
3
3
  import type { AppEntity, AppState } from "../../types"
4
4
  // @ts-expect-error - Handled by @inglorious/vite-plugin-vue
@@ -7,7 +7,7 @@ import { entities } from "./entities"
7
7
  import { middlewares } from "./middlewares"
8
8
 
9
9
  export const store = createStore<AppEntity, AppState>({
10
- types: { Message } as unknown as TypesConfig<AppEntity>,
10
+ types: { Message },
11
11
  entities,
12
12
  middlewares,
13
13
  })
@@ -1,12 +1,11 @@
1
1
  import "./vite-end"
2
2
 
3
- import { BaseEntity, EntitiesState } from "@inglorious/store"
4
- import { Api, TemplateResult } from "@inglorious/web"
3
+ import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"
4
+ import type { Api } from "@inglorious/web"
5
5
 
6
6
  // Define your entity types
7
7
  export interface MessageEntity extends BaseEntity {
8
8
  type: "Message"
9
- id?: string
10
9
  who: string
11
10
  isUpperCase?: boolean
12
11
  }
@@ -23,8 +22,7 @@ export interface AppState extends EntitiesState<AppEntity> {
23
22
 
24
23
  export type AppApi = Api<AppEntity, AppState>
25
24
 
26
- // Types configuration
27
- export interface MessageType {
28
- click: (entity: MessageEntity) => void
29
- render: (entity: MessageEntity, api: AppApi) => TemplateResult | null
30
- }
25
+ // A type for one of the app's entities. For rigour, declare a type alias
26
+ // listing its events and payloads; this shorthand infers its handlers'
27
+ // parameters with `{ ... } satisfies AppType<MessageEntity>`.
28
+ export type AppType<TEntity extends AppEntity> = EntityType<TEntity, AppApi>