@inglorious/create-app 1.9.1 → 1.11.0

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 (41) hide show
  1. package/bin/lib/app-jsx.js +22 -7
  2. package/bin/lib/app-lit.js +23 -7
  3. package/bin/lib/app-vue.js +14 -6
  4. package/bin/lib/app.js +85 -23
  5. package/bin/lib/prompts.js +17 -11
  6. package/bin/lib/source.js +26 -0
  7. package/package.json +1 -1
  8. package/templates/js/src/store/index.js +1 -1
  9. package/templates/js-jsx/src/{app.jsx → app/index.jsx} +1 -1
  10. package/templates/js-jsx/src/store/index.js +1 -1
  11. package/templates/{ts-vue/src/app.vue → js-vue/src/app/index.vue} +1 -1
  12. package/templates/js-vue/src/main.js +1 -1
  13. package/templates/js-vue/src/store/index.js +1 -1
  14. package/templates/minimal/src/main.js +1 -1
  15. package/templates/minimal/src/store/index.js +1 -1
  16. package/templates/ssx-ts/types/index.d.ts +10 -2
  17. package/templates/ssx-ts-jsx/types/index.d.ts +10 -2
  18. package/templates/ts/src/{app.ts → app/index.ts} +2 -2
  19. package/templates/ts/src/{types/message.ts → app/message/index.ts} +1 -1
  20. package/templates/ts/src/main.ts +6 -1
  21. package/templates/ts/src/store/index.ts +3 -3
  22. package/templates/ts/types/index.d.ts +11 -3
  23. package/templates/ts-jsx/src/{app.tsx → app/index.tsx} +2 -3
  24. package/templates/ts-jsx/src/{types/message.tsx → app/message/index.tsx} +1 -1
  25. package/templates/ts-jsx/src/main.ts +1 -1
  26. package/templates/ts-jsx/src/store/index.ts +3 -3
  27. package/templates/ts-jsx/types/index.d.ts +11 -3
  28. package/templates/{js-vue/src/app.vue → ts-vue/src/app/index.vue} +1 -1
  29. package/templates/ts-vue/src/main.ts +7 -2
  30. package/templates/ts-vue/src/store/index.ts +1 -1
  31. package/templates/ts-vue/types/index.d.ts +1 -1
  32. /package/templates/js/src/{app.js → app/index.js} +0 -0
  33. /package/templates/js/src/{types/message.js → app/message/index.js} +0 -0
  34. /package/templates/js-jsx/src/{types/message.jsx → app/message/index.jsx} +0 -0
  35. /package/templates/js-vue/src/{types/message.vue → app/message/index.vue} +0 -0
  36. /package/templates/minimal/src/{app.js → app/index.js} +0 -0
  37. /package/templates/minimal/src/{types/message.js → app/message/index.js} +0 -0
  38. /package/templates/ts/types/{vite-end.d.ts → vite-env.d.ts} +0 -0
  39. /package/templates/ts-jsx/types/{vite-end.d.ts → vite-env.d.ts} +0 -0
  40. /package/templates/ts-vue/src/{types/message.vue → app/message/index.vue} +0 -0
  41. /package/templates/ts-vue/types/{vite-end.d.ts → vite-env.d.ts} +0 -0
@@ -1,5 +1,6 @@
1
1
  import { renderLocals, selectChange } from "./app-lit.js"
2
2
  import { importBlock } from "./imports.js"
3
+ import { relative } from "./source.js"
3
4
 
4
5
  export const extension = (app) => (app.isTypeScript ? "tsx" : "jsx")
5
6
 
@@ -27,8 +28,12 @@ export function appSource(app, { pages, navIcons }) {
27
28
  if (app.links) imports.push('import { Link } from "@inglorious/ui/link"')
28
29
  if (app.i18n) imports.push('import { Select } from "@inglorious/ui/select"')
29
30
  }
30
- if (app.isTypeScript) imports.push('import type { AppApi } from "../types"')
31
- if (app.i18n) imports.push('import { t } from "./i18n"')
31
+ if (app.isTypeScript)
32
+ imports.push(
33
+ `import type { AppApi } from "${relative(app.layout.app, "types")}"`,
34
+ )
35
+ if (app.i18n)
36
+ imports.push(`import { t } from "${relative(app.layout.app, "src/i18n")}"`)
32
37
 
33
38
  const signature = app.isTypeScript
34
39
  ? "render(props: Record<string, unknown> | null, api: AppApi)"
@@ -56,14 +61,17 @@ ${shell}
56
61
  }
57
62
 
58
63
  export function homeSource(app, { showcase }) {
59
- const imports = ['import { Message } from "../types/message"']
64
+ const imports = [
65
+ `import { Message } from "${relative(app.layout.home, app.layout.message)}"`,
66
+ ]
60
67
  for (const icon of showcase) {
61
68
  imports.push(`import ${icon.name} from "${icon.from}"`)
62
69
  }
63
70
  if (showcase.length) {
64
71
  imports.push('import { Icon } from "@inglorious/ui/icon"')
65
72
  }
66
- if (app.i18n) imports.push('import { t } from "../i18n"')
73
+ if (app.i18n)
74
+ imports.push(`import { t } from "${relative(app.layout.home, "src/i18n")}"`)
67
75
 
68
76
  const text = (key, fallback) => (app.i18n ? `{t("${key}")}` : fallback)
69
77
  const icons = showcase.length
@@ -98,7 +106,9 @@ ${expectError(app, " ")} <Message entityId="message3" />!
98
106
  }
99
107
 
100
108
  export function aboutSource(app) {
101
- const head = app.i18n ? 'import { t } from "../i18n"\n\n' : ""
109
+ const head = app.i18n
110
+ ? `import { t } from "${relative(app.layout.about, "src/i18n")}"\n\n`
111
+ : ""
102
112
  const text = (key, fallback) => (app.i18n ? `{t("${key}")}` : fallback)
103
113
 
104
114
  return `${head}export const About = {
@@ -117,9 +127,14 @@ export function aboutSource(app) {
117
127
  export function messageSource(app) {
118
128
  const imports = []
119
129
  if (app.isTypeScript) {
120
- imports.push('import type { MessageType } from "../../types"')
130
+ imports.push(
131
+ `import type { MessageType } from "${relative(app.layout.message, "types")}"`,
132
+ )
121
133
  }
122
- if (app.i18n) imports.push('import { t } from "../i18n"')
134
+ if (app.i18n)
135
+ imports.push(
136
+ `import { t } from "${relative(app.layout.message, "src/i18n")}"`,
137
+ )
123
138
  const greeting = app.i18n ? '{t("message.hello", { who })}' : "Hello {who}"
124
139
  const annotation = app.isTypeScript ? ": MessageType" : ""
125
140
 
@@ -1,4 +1,5 @@
1
1
  import { importBlock } from "./imports.js"
2
+ import { relative } from "./source.js"
2
3
 
3
4
  export const extension = (app) => (app.isTypeScript ? "ts" : "js")
4
5
 
@@ -23,8 +24,12 @@ export function appSource(app, { pages, navIcons }) {
23
24
  if (app.links) imports.push('import { Link } from "@inglorious/ui/link"')
24
25
  if (app.i18n) imports.push('import { Select } from "@inglorious/ui/select"')
25
26
  }
26
- if (app.isTypeScript) imports.push('import type { AppApi } from "../types"')
27
- if (app.i18n) imports.push('import { t } from "./i18n"')
27
+ if (app.isTypeScript)
28
+ imports.push(
29
+ `import type { AppApi } from "${relative(app.layout.app, "types")}"`,
30
+ )
31
+ if (app.i18n)
32
+ imports.push(`import { t } from "${relative(app.layout.app, "src/i18n")}"`)
28
33
 
29
34
  const label = (key, text) => (app.i18n ? `t("${key}")` : `"${text}"`)
30
35
  const signature = app.isTypeScript
@@ -82,9 +87,12 @@ export function homeSource(app, { showcase }) {
82
87
  imports.push('import { Icon } from "@inglorious/ui/icon"')
83
88
  }
84
89
  if (app.isTypeScript) {
85
- imports.push('import type { HomeType } from "../../types"')
90
+ imports.push(
91
+ `import type { HomeType } from "${relative(app.layout.home, "types")}"`,
92
+ )
86
93
  }
87
- if (app.i18n) imports.push('import { t } from "../i18n"')
94
+ if (app.i18n)
95
+ imports.push(`import { t } from "${relative(app.layout.home, "src/i18n")}"`)
88
96
 
89
97
  const text = (key, fallback) => (app.i18n ? `\${t("${key}")}` : fallback)
90
98
  const icons = showcase.length
@@ -123,7 +131,10 @@ export const Home${annotation} = {
123
131
 
124
132
  export function aboutSource(app) {
125
133
  const imports = ['import { html } from "@inglorious/web"']
126
- if (app.i18n) imports.push('import { t } from "../i18n"')
134
+ if (app.i18n)
135
+ imports.push(
136
+ `import { t } from "${relative(app.layout.about, "src/i18n")}"`,
137
+ )
127
138
  const text = (key, fallback) => (app.i18n ? `\${t("${key}")}` : fallback)
128
139
 
129
140
  return `${importBlock(imports)}
@@ -142,9 +153,14 @@ export const About = {
142
153
  export function messageSource(app) {
143
154
  const imports = ['import { html } from "@inglorious/web"']
144
155
  if (app.isTypeScript) {
145
- imports.push('import type { MessageType } from "../../types"')
156
+ imports.push(
157
+ `import type { MessageType } from "${relative(app.layout.message, "types")}"`,
158
+ )
146
159
  }
147
- if (app.i18n) imports.push('import { t } from "../i18n"')
160
+ if (app.i18n)
161
+ imports.push(
162
+ `import { t } from "${relative(app.layout.message, "src/i18n")}"`,
163
+ )
148
164
  const greeting = app.i18n ? '${t("message.hello", { who })}' : "Hello ${who}"
149
165
  const annotation = app.isTypeScript ? ": MessageType" : ""
150
166
 
@@ -1,5 +1,5 @@
1
1
  import { importBlock } from "./imports.js"
2
- import { LINE_WIDTH } from "./source.js"
2
+ import { LINE_WIDTH, relative } from "./source.js"
3
3
 
4
4
  export const extension = () => "vue"
5
5
 
@@ -24,7 +24,8 @@ export function appSource(app, { pages, navIcons }) {
24
24
  if (app.links) imports.push('import { Link } from "@inglorious/ui/link"')
25
25
  if (app.i18n) imports.push('import { Select } from "@inglorious/ui/select"')
26
26
  }
27
- if (app.i18n) imports.push('import { t } from "./i18n"')
27
+ if (app.i18n)
28
+ imports.push(`import { t } from "${relative(app.layout.app, "src/i18n")}"`)
28
29
 
29
30
  const locals = []
30
31
  if (app.routing) locals.push('const router = api.getEntity("router")')
@@ -47,14 +48,17 @@ ${template}
47
48
  }
48
49
 
49
50
  export function homeSource(app, { showcase }) {
50
- const imports = ['import { Message } from "../types/message.vue"']
51
+ const imports = [
52
+ `import { Message } from "${relative(app.layout.home, app.layout.message)}/index.vue"`,
53
+ ]
51
54
  for (const icon of showcase) {
52
55
  imports.push(`import ${icon.name} from "${icon.from}"`)
53
56
  }
54
57
  if (showcase.length) {
55
58
  imports.push('import { Icon } from "@inglorious/ui/icon"')
56
59
  }
57
- if (app.i18n) imports.push('import { t } from "../i18n"')
60
+ if (app.i18n)
61
+ imports.push(`import { t } from "${relative(app.layout.home, "src/i18n")}"`)
58
62
 
59
63
  const text = (key, fallback) => (app.i18n ? `{{ t("${key}") }}` : fallback)
60
64
  const icons = showcase.length
@@ -81,7 +85,9 @@ ${showcase
81
85
  }
82
86
 
83
87
  export function aboutSource(app) {
84
- const imports = app.i18n ? ['import { t } from "../i18n"'] : []
88
+ const imports = app.i18n
89
+ ? [`import { t } from "${relative(app.layout.about, "src/i18n")}"`]
90
+ : []
85
91
  const text = (key, fallback) => (app.i18n ? `{{ t("${key}") }}` : fallback)
86
92
  return `${script(imports, [])}<template>
87
93
  <h1>${text("about.title", "About")}</h1>
@@ -91,7 +97,9 @@ export function aboutSource(app) {
91
97
  }
92
98
 
93
99
  export function messageSource(app) {
94
- const imports = app.i18n ? ['import { t } from "../i18n"'] : []
100
+ const imports = app.i18n
101
+ ? [`import { t } from "${relative(app.layout.message, "src/i18n")}"`]
102
+ : []
95
103
  const who = "entity.isUpperCase ? entity.who.toUpperCase() : entity.who"
96
104
  const span = app.i18n
97
105
  ? ` <span @click="click">{{
package/bin/lib/app.js CHANGED
@@ -15,7 +15,14 @@ import {
15
15
  import { ICON_PACKS, LANGUAGES, PROMPT_TEXTS } from "./features.js"
16
16
  import { importBlock } from "./imports.js"
17
17
  import { envExample, promptFiles } from "./prompts.js"
18
- import { appModel, call, LINE_WIDTH, union } from "./source.js"
18
+ import {
19
+ appModel,
20
+ call,
21
+ layout,
22
+ LINE_WIDTH,
23
+ relative,
24
+ union,
25
+ } from "./source.js"
19
26
 
20
27
  const COMPONENTS = {
21
28
  "lit-html": litComponents,
@@ -53,6 +60,7 @@ export function addAppFeatures(targetDir, pkg, options) {
53
60
  app.links = app.routing && !app.tabs
54
61
  app.isVue = app.renderer === "vue"
55
62
  app.hasPrompts = app.analytics || app.push
63
+ app.layout = layout(app)
56
64
 
57
65
  const components = COMPONENTS[app.renderer]
58
66
  const componentExt = components.extension(app)
@@ -60,17 +68,18 @@ export function addAppFeatures(targetDir, pkg, options) {
60
68
 
61
69
  addDependencies(pkg, app)
62
70
 
71
+ const { layout: at } = app
63
72
  const files = {
64
73
  [`src/main.${app.ext}`]: mainSource(app),
65
- [`src/app.${componentExt}`]: components.appSource(app, model),
66
- "src/app.css": appStyles(app),
67
- [`src/pages/home.${componentExt}`]: components.homeSource(app, model),
68
- [`src/types/message.${componentExt}`]: components.messageSource(app),
74
+ [`${at.app}/index.${componentExt}`]: components.appSource(app, model),
75
+ [`${at.app}/style.css`]: appStyles(app),
76
+ [`${at.home}/index.${componentExt}`]: components.homeSource(app, model),
77
+ [`${at.message}/index.${componentExt}`]: components.messageSource(app),
69
78
  [`src/store/index.${app.ext}`]: storeSource(app),
70
79
  [`src/store/entities.${app.ext}`]: entitiesSource(app),
71
80
  }
72
81
  if (app.routing) {
73
- files[`src/pages/about.${componentExt}`] = components.aboutSource(app)
82
+ files[`${at.about}/index.${componentExt}`] = components.aboutSource(app)
74
83
  files[`src/store/routes.${app.ext}`] = routesSource(app)
75
84
  }
76
85
  if (app.i18n) {
@@ -88,6 +97,10 @@ export function addAppFeatures(targetDir, pkg, options) {
88
97
  files["types/index.d.ts"] = typesSource(app)
89
98
  }
90
99
 
100
+ // The template's hello world keeps its messages in the shell; here they move
101
+ // to the home screen that renders them.
102
+ removeTemplateMessages(targetDir, app)
103
+
91
104
  for (const [file, source] of Object.entries(files)) {
92
105
  const filePath = path.join(targetDir, file)
93
106
  fs.mkdirSync(path.dirname(filePath), { recursive: true })
@@ -97,6 +110,15 @@ export function addAppFeatures(targetDir, pkg, options) {
97
110
  if (app.ui) applyTheme(targetDir, app)
98
111
  }
99
112
 
113
+ function removeTemplateMessages(targetDir, app) {
114
+ const templateMessages = "src/app/message"
115
+ if (app.layout.message === templateMessages) return
116
+ fs.rmSync(path.join(targetDir, templateMessages), {
117
+ recursive: true,
118
+ force: true,
119
+ })
120
+ }
121
+
100
122
  function addDependencies(pkg, app) {
101
123
  pkg.dependencies ??= {}
102
124
  if (app.ui) pkg.dependencies["@inglorious/ui"] = "workspace:*"
@@ -141,9 +163,10 @@ function applyTheme(targetDir, app) {
141
163
 
142
164
  // An import of one of the app's components: Vue's name their file, and
143
165
  // TypeScript cannot read .vue files, so it is told to expect that.
166
+ /** Imports a component from its folder: its index, which a .vue file names. */
144
167
  function componentImport(app, names, from) {
145
168
  if (!app.isVue) return `import { ${names} } from "${from}"`
146
- const line = `import { ${names} } from "${from}.vue"`
169
+ const line = `import { ${names} } from "${from}/index.vue"`
147
170
  return app.isTypeScript
148
171
  ? `// @ts-expect-error - Handled by @inglorious/vite-plugin-vue\n${line}`
149
172
  : line
@@ -161,10 +184,10 @@ function mainSource(app) {
161
184
  imports.push('import "@inglorious/ui/view-transitions.css"')
162
185
  }
163
186
  }
164
- imports.push('import "./app.css"')
187
+ imports.push(`import "${relative("src", app.layout.app)}/style.css"`)
165
188
  imports.push(
166
189
  `import { ${app.hasPrompts ? "html, mount" : "mount"} } from "@inglorious/web"`,
167
- componentImport(app, "App", "./app"),
190
+ componentImport(app, "App", relative("src", app.layout.app)),
168
191
  'import { store } from "./store"',
169
192
  )
170
193
  if (app.i18n) imports.push('import { ready } from "./i18n"')
@@ -292,8 +315,8 @@ main {
292
315
  function storeSource(app) {
293
316
  const imports = [
294
317
  'import { createStore } from "@inglorious/store"',
295
- componentImport(app, "Home", "../pages/home"),
296
- componentImport(app, "Message", "../types/message"),
318
+ componentImport(app, "Home", relative("src/store", app.layout.home)),
319
+ componentImport(app, "Message", relative("src/store", app.layout.message)),
297
320
  'import { entities } from "./entities"',
298
321
  'import { middlewares } from "./middlewares"',
299
322
  ]
@@ -305,19 +328,25 @@ function storeSource(app) {
305
328
  if (app.routing) {
306
329
  imports.push(
307
330
  'import { Router, setRoutes } from "@inglorious/web/router"',
308
- componentImport(app, "About", "../pages/about"),
331
+ componentImport(app, "About", relative("src/store", app.layout.about)),
309
332
  'import { routes } from "./routes"',
310
333
  )
311
334
  }
312
335
  if (app.isTypeScript) {
313
- imports.push('import type { AppEntity, AppState } from "../../types"')
336
+ imports.push(
337
+ 'import type { AppEntity, AppEvents, AppState } from "../../types"',
338
+ )
314
339
  }
315
340
  if (app.i18n) imports.push('import { I18n } from "../i18n"')
316
341
  if (app.analytics) {
317
- imports.push('import { Analytics } from "../types/analytics"')
342
+ imports.push(
343
+ `import { Analytics } from "${relative("src/store", app.layout.analytics)}"`,
344
+ )
318
345
  }
319
346
  if (app.push) {
320
- imports.push('import { PushPrompt } from "../types/push-prompt"')
347
+ imports.push(
348
+ `import { PushPrompt } from "${relative("src/store", app.layout.pushPrompt)}"`,
349
+ )
321
350
  }
322
351
 
323
352
  const types = ["Message", "Home"]
@@ -332,7 +361,7 @@ function storeSource(app) {
332
361
  : ` types: {
333
362
  ${types.map((type) => ` ${type},`).join("\n")}
334
363
  },`
335
- const generics = app.isTypeScript ? "<AppEntity, AppState>" : ""
364
+ const generics = app.isTypeScript ? "<AppEntity, AppState, AppEvents>" : ""
336
365
 
337
366
  return `${importBlock(imports)}
338
367
  ${app.routing ? "\nsetRoutes(routes)\n" : ""}
@@ -398,13 +427,35 @@ export const { I18n, t, ready } = createI18n({
398
427
  `
399
428
  }
400
429
 
430
+ /**
431
+ * `export type AppEvents = EventsOf<...>`, wrapped as Prettier would: on one
432
+ * line if it fits, then with the types on a line of their own, then one per
433
+ * line.
434
+ */
435
+ function eventsOf(types) {
436
+ const head = "export type AppEvents = EventsOf<"
437
+ const oneLine = `${head}${types.join(" | ")}>`
438
+ if (oneLine.length <= LINE_WIDTH) return oneLine
439
+ const inner = ` ${types.join(" | ")}`
440
+ if (inner.length <= LINE_WIDTH) return `${head}\n${inner}\n>`
441
+ return `${head}\n${types.map((type) => ` | ${type}`).join("\n")}\n>`
442
+ }
443
+
401
444
  function typesSource(app) {
402
445
  const imports = [
403
- 'import "./vite-end"',
404
- 'import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"',
446
+ 'import "./vite-env"',
447
+ `import type {
448
+ BaseEntity,
449
+ EntitiesState,
450
+ EntityType,
451
+ EventsOf,
452
+ } from "@inglorious/store"`,
405
453
  'import type { Api, TemplateResult } from "@inglorious/web"',
406
454
  ]
407
455
  const entities = ["MessageEntity", "HomeEntity"]
456
+ // The types whose events the app sends: its own, then the libraries'.
457
+ const eventTypes = ["MessageType"]
458
+ if (app.renderer === "lit-html") eventTypes.push("HomeType")
408
459
  const declarations = [
409
460
  `export interface MessageEntity extends BaseEntity {
410
461
  type: "Message"
@@ -423,7 +474,10 @@ function typesSource(app) {
423
474
  ]
424
475
 
425
476
  if (app.routing) {
426
- imports.push('import type { RouterEntity } from "@inglorious/web/router"')
477
+ imports.push(
478
+ 'import type { RouterEntity, RouterType } from "@inglorious/web/router"',
479
+ )
480
+ eventTypes.push("RouterType")
427
481
  entities.push("AboutEntity", "AppRouterEntity")
428
482
  declarations.push(
429
483
  `export interface AboutEntity extends BaseEntity {
@@ -437,7 +491,8 @@ function typesSource(app) {
437
491
  state.push(" about: AboutEntity")
438
492
  }
439
493
  if (app.i18n) {
440
- imports.push('import type { I18nEntity } from "@inglorious/i18n"')
494
+ imports.push('import type { I18nEntity, I18nType } from "@inglorious/i18n"')
495
+ eventTypes.push("I18nType")
441
496
  entities.push("AppI18nEntity")
442
497
  declarations.push(
443
498
  `export interface AppI18nEntity extends I18nEntity {
@@ -448,8 +503,9 @@ function typesSource(app) {
448
503
  }
449
504
  if (app.analytics) {
450
505
  imports.push(
451
- 'import type { AnalyticsEntity } from "@inglorious/web/analytics"',
506
+ 'import type { AnalyticsEntity, AnalyticsType } from "@inglorious/web/analytics"',
452
507
  )
508
+ eventTypes.push("AnalyticsType")
453
509
  entities.push("AppAnalyticsEntity")
454
510
  declarations.push(
455
511
  `export interface AppAnalyticsEntity extends AnalyticsEntity {
@@ -459,7 +515,10 @@ function typesSource(app) {
459
515
  state.push(" analytics: AppAnalyticsEntity")
460
516
  }
461
517
  if (app.push) {
462
- imports.push('import type { PushPromptEntity } from "@inglorious/web/push"')
518
+ imports.push(
519
+ 'import type { PushPromptEntity, PushPromptType } from "@inglorious/web/push"',
520
+ )
521
+ eventTypes.push("PushPromptType")
463
522
  entities.push("AppPushPromptEntity")
464
523
  declarations.push(
465
524
  `export interface AppPushPromptEntity extends PushPromptEntity {
@@ -481,7 +540,10 @@ export interface AppState extends EntitiesState<AppEntity> {
481
540
  ${state.join("\n")}
482
541
  }
483
542
 
484
- export type AppApi = Api<AppEntity, AppState>
543
+ // The events the app's types answer, so that \`notify\` checks them.
544
+ ${eventsOf(eventTypes)}
545
+
546
+ export type AppApi = Api<AppEntity, AppState, AppEvents>
485
547
 
486
548
  // Each type's events and their payloads. Declare types with \`type\`, not
487
549
  // \`interface\`: an interface does not fit the store's \`types\`.
@@ -1,5 +1,5 @@
1
1
  import { importBlock } from "./imports.js"
2
- import { LINE_WIDTH } from "./source.js"
2
+ import { LINE_WIDTH, relative } from "./source.js"
3
3
 
4
4
  const FIREBASE_CONFIG_KEYS = {
5
5
  apiKey: "VITE_FIREBASE_API_KEY",
@@ -22,12 +22,12 @@ export function promptFiles(app) {
22
22
  [`src/common/prompt.${app.ext}`]: promptSource(app),
23
23
  }
24
24
  if (app.analytics) {
25
- files[`src/types/analytics.${app.ext}`] = analyticsSource(app)
25
+ files[`${app.layout.analytics}/index.${app.ext}`] = analyticsSource(app)
26
26
  }
27
27
  if (app.push) {
28
28
  files[`src/api/supabase.${app.ext}`] = supabaseSource()
29
29
  files[`src/api/push.${app.ext}`] = pushSource(app)
30
- files[`src/types/push-prompt.${app.ext}`] = pushPromptSource(app)
30
+ files[`${app.layout.pushPrompt}/index.${app.ext}`] = pushPromptSource(app)
31
31
  }
32
32
  return files
33
33
  }
@@ -172,13 +172,16 @@ function analyticsSource(app) {
172
172
  'import { firebaseAnalytics } from "@inglorious/firebase/analytics"',
173
173
  'import { createAnalytics } from "@inglorious/web/analytics"',
174
174
  'import { when } from "@inglorious/web/directives/when"',
175
- 'import { firebaseConfig } from "../api/firebase"',
176
- 'import { Prompt } from "../common/prompt"',
175
+ `import { firebaseConfig } from "${relative(app.layout.analytics, "src/api/firebase")}"`,
176
+ `import { Prompt } from "${relative(app.layout.analytics, "src/common/prompt")}"`,
177
177
  ]
178
- if (app.i18n) imports.push('import { t } from "../i18n"')
178
+ if (app.i18n)
179
+ imports.push(
180
+ `import { t } from "${relative(app.layout.analytics, "src/i18n")}"`,
181
+ )
179
182
  if (app.isTypeScript) {
180
183
  imports.push(
181
- 'import type { AppAnalyticsEntity, AppApi } from "../../types"',
184
+ `import type { AppAnalyticsEntity, AppApi } from "${relative(app.layout.analytics, "types")}"`,
182
185
  )
183
186
  }
184
187
  const label = labelFor(app)
@@ -263,13 +266,16 @@ function pushPromptSource(app) {
263
266
  const imports = [
264
267
  'import { when } from "@inglorious/web/directives/when"',
265
268
  'import { createPushPrompt } from "@inglorious/web/push"',
266
- 'import { push } from "../api/push"',
267
- 'import { Prompt } from "../common/prompt"',
269
+ `import { push } from "${relative(app.layout.pushPrompt, "src/api/push")}"`,
270
+ `import { Prompt } from "${relative(app.layout.pushPrompt, "src/common/prompt")}"`,
268
271
  ]
269
- if (app.i18n) imports.push('import { t } from "../i18n"')
272
+ if (app.i18n)
273
+ imports.push(
274
+ `import { t } from "${relative(app.layout.pushPrompt, "src/i18n")}"`,
275
+ )
270
276
  if (app.isTypeScript) {
271
277
  imports.push(
272
- 'import type { AppApi, AppPushPromptEntity } from "../../types"',
278
+ `import type { AppApi, AppPushPromptEntity } from "${relative(app.layout.pushPrompt, "types")}"`,
273
279
  )
274
280
  }
275
281
  const label = labelFor(app)
package/bin/lib/source.js CHANGED
@@ -1,3 +1,5 @@
1
+ import path from "path"
2
+
1
3
  import { ICON_PACKS } from "./features.js"
2
4
 
3
5
  export const LINE_WIDTH = 80
@@ -25,6 +27,30 @@ export function appModel(app) {
25
27
  }
26
28
  }
27
29
 
30
+ /**
31
+ * Where each part of a generated app lives. The shell and its own pieces are
32
+ * in src/app; routed screens are in src/views, and without routing the one
33
+ * screen is a piece of the shell. A piece lives with what renders it, so the
34
+ * messages live in the home screen's folder.
35
+ */
36
+ export function layout(app) {
37
+ const screens = app.routing ? "src/views" : "src/app"
38
+ return {
39
+ app: "src/app",
40
+ home: `${screens}/home`,
41
+ about: "src/views/about",
42
+ message: `${screens}/home/message`,
43
+ analytics: "src/app/analytics",
44
+ pushPrompt: "src/app/push-prompt",
45
+ }
46
+ }
47
+
48
+ /** The import specifier from a folder to a path, both relative to the project. */
49
+ export function relative(fromDir, to) {
50
+ const specifier = path.posix.relative(fromDir, to)
51
+ return specifier.startsWith(".") ? specifier : `./${specifier}`
52
+ }
53
+
28
54
  export function call(name, args, indent, trailing) {
29
55
  const line = `${indent}${name}(${args.join(", ")})${trailing}`
30
56
  if (line.length <= LINE_WIDTH) return line
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/create-app",
3
- "version": "1.9.1",
3
+ "version": "1.11.0",
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",
@@ -1,6 +1,6 @@
1
1
  import { createStore } from "@inglorious/store"
2
2
 
3
- import { Message } from "../types/message"
3
+ import { Message } from "../app/message"
4
4
  import { entities } from "./entities"
5
5
  import { middlewares } from "./middlewares"
6
6
 
@@ -1,4 +1,4 @@
1
- import { Message } from "./types/message"
1
+ import { Message } from "./message"
2
2
 
3
3
  export const App = {
4
4
  render() {
@@ -1,6 +1,6 @@
1
1
  import { createStore } from "@inglorious/store"
2
2
 
3
- import { Message } from "../types/message"
3
+ import { Message } from "../app/message"
4
4
  import { entities } from "./entities"
5
5
  import { middlewares } from "./middlewares"
6
6
 
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import { Message } from "./types/message.vue"
2
+ import { Message } from "./message/index.vue"
3
3
  </script>
4
4
 
5
5
  <template>
@@ -1,6 +1,6 @@
1
1
  import { mount } from "@inglorious/web"
2
2
 
3
- import { App } from "./app.vue"
3
+ import { App } from "./app/index.vue"
4
4
  import { store } from "./store"
5
5
 
6
6
  mount(store, (api) => App.render(null, api), document.getElementById("root"))
@@ -1,6 +1,6 @@
1
1
  import { createStore } from "@inglorious/store"
2
2
 
3
- import { Message } from "../types/message.vue"
3
+ import { Message } from "../app/message/index.vue"
4
4
  import { entities } from "./entities"
5
5
  import { middlewares } from "./middlewares"
6
6
 
@@ -1,6 +1,6 @@
1
1
  import { mount } from "@inglorious/web"
2
2
 
3
- import { App } from "./app.js"
3
+ import { App } from "./app/index.js"
4
4
  import { store } from "./store/index.js"
5
5
 
6
6
  mount(store, (api) => App.render(null, api), document.getElementById("root"))
@@ -1,6 +1,6 @@
1
1
  import { createStore } from "@inglorious/store"
2
2
 
3
- import { Message } from "../types/message.js"
3
+ import { Message } from "../app/message/index.js"
4
4
  import { entities } from "./entities.js"
5
5
  import { middlewares } from "./middlewares.js"
6
6
 
@@ -1,4 +1,9 @@
1
- import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"
1
+ import type {
2
+ BaseEntity,
3
+ EntitiesState,
4
+ EntityType,
5
+ EventsOf,
6
+ } from "@inglorious/store"
2
7
  import type { Api, TemplateResult } from "@inglorious/web"
3
8
 
4
9
  export interface AboutEntity extends BaseEntity {
@@ -12,7 +17,10 @@ export interface AppState extends EntitiesState<AppEntity> {
12
17
  about: AboutEntity
13
18
  }
14
19
 
15
- export type AppApi = Api<AppEntity, AppState>
20
+ // The events the types below answer, so that `notify` checks them.
21
+ export type AppEvents = EventsOf<AboutType>
22
+
23
+ export type AppApi = Api<AppEntity, AppState, AppEvents>
16
24
 
17
25
  export type AboutType = {
18
26
  click: (entity: AboutEntity) => void
@@ -1,6 +1,11 @@
1
1
  /// <reference types="@inglorious/jsx/jsx-runtime" />
2
2
 
3
- import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"
3
+ import type {
4
+ BaseEntity,
5
+ EntitiesState,
6
+ EntityType,
7
+ EventsOf,
8
+ } from "@inglorious/store"
4
9
  import type { Api, TemplateResult } from "@inglorious/web"
5
10
 
6
11
  export interface AboutEntity extends BaseEntity {
@@ -14,7 +19,10 @@ export interface AppState extends EntitiesState<AppEntity> {
14
19
  about: AboutEntity
15
20
  }
16
21
 
17
- export type AppApi = Api<AppEntity, AppState>
22
+ // The events the types below answer, so that `notify` checks them.
23
+ export type AppEvents = EventsOf<AboutType>
24
+
25
+ export type AppApi = Api<AppEntity, AppState, AppEvents>
18
26
 
19
27
  export type AboutType = {
20
28
  click: (entity: AboutEntity) => void
@@ -1,9 +1,9 @@
1
1
  import { html } from "@inglorious/web"
2
2
 
3
- import type { AppApi } from "../types"
3
+ import type { AppApi } from "../../types"
4
4
 
5
5
  export const App = {
6
- render(props: Record<string, unknown> | null, api: AppApi) {
6
+ render(_props: Record<string, unknown> | null, api: AppApi) {
7
7
  return html`<h1>
8
8
  ${api.render("message1")}, ${api.render("message2")},
9
9
  ${api.render("message3")}!
@@ -1,6 +1,6 @@
1
1
  import { html } from "@inglorious/web"
2
2
 
3
- import type { MessageType } from "../../types"
3
+ import type { MessageType } from "../../../types"
4
4
 
5
5
  export const Message: MessageType = {
6
6
  click(entity) {
@@ -1,6 +1,11 @@
1
1
  import { mount } from "@inglorious/web"
2
2
 
3
+ import type { AppApi } from "../types"
3
4
  import { App } from "./app"
4
5
  import { store } from "./store"
5
6
 
6
- mount(store, (api) => App.render(null, api), document.getElementById("root")!)
7
+ mount(
8
+ store,
9
+ (api: AppApi) => App.render(null, api),
10
+ document.getElementById("root")!,
11
+ )
@@ -1,11 +1,11 @@
1
1
  import { createStore } from "@inglorious/store"
2
2
 
3
- import type { AppEntity, AppState } from "../../types"
4
- import { Message } from "../types/message"
3
+ import type { AppEntity, AppEvents, AppState } from "../../types"
4
+ import { Message } from "../app/message"
5
5
  import { entities } from "./entities"
6
6
  import { middlewares } from "./middlewares"
7
7
 
8
- export const store = createStore<AppEntity, AppState>({
8
+ export const store = createStore<AppEntity, AppState, AppEvents>({
9
9
  types: { Message },
10
10
  entities,
11
11
  middlewares,
@@ -1,6 +1,11 @@
1
- import "./vite-end"
1
+ import "./vite-env"
2
2
 
3
- import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"
3
+ import type {
4
+ BaseEntity,
5
+ EntitiesState,
6
+ EntityType,
7
+ EventsOf,
8
+ } from "@inglorious/store"
4
9
  import type { Api, TemplateResult } from "@inglorious/web"
5
10
 
6
11
  export interface MessageEntity extends BaseEntity {
@@ -17,7 +22,10 @@ export interface AppState extends EntitiesState<AppEntity> {
17
22
  message3: MessageEntity
18
23
  }
19
24
 
20
- export type AppApi = Api<AppEntity, AppState>
25
+ // The events the types below answer, so that `notify` checks them.
26
+ export type AppEvents = EventsOf<MessageType>
27
+
28
+ export type AppApi = Api<AppEntity, AppState, AppEvents>
21
29
 
22
30
  export type MessageType = {
23
31
  click: (entity: MessageEntity) => void
@@ -1,8 +1,7 @@
1
- import type { AppApi } from "../types"
2
- import { Message } from "./types/message"
1
+ import { Message } from "./message"
3
2
 
4
3
  export const App = {
5
- render(props: Record<string, unknown> | null, api: AppApi) {
4
+ render() {
6
5
  return (
7
6
  <h1>
8
7
  {/* @ts-expect-error - JSX components are entity type names, not imports */}
@@ -1,4 +1,4 @@
1
- import type { MessageType } from "../../types"
1
+ import type { MessageType } from "../../../types"
2
2
 
3
3
  export const Message: MessageType = {
4
4
  click(entity) {
@@ -3,4 +3,4 @@ import { mount } from "@inglorious/web"
3
3
  import { App } from "./app"
4
4
  import { store } from "./store"
5
5
 
6
- mount(store, (api) => App.render(null, api), document.getElementById("root")!)
6
+ mount(store, App.render, document.getElementById("root")!)
@@ -1,11 +1,11 @@
1
1
  import { createStore } from "@inglorious/store"
2
2
 
3
- import type { AppEntity, AppState } from "../../types"
4
- import { Message } from "../types/message"
3
+ import type { AppEntity, AppEvents, AppState } from "../../types"
4
+ import { Message } from "../app/message"
5
5
  import { entities } from "./entities"
6
6
  import { middlewares } from "./middlewares"
7
7
 
8
- export const store = createStore<AppEntity, AppState>({
8
+ export const store = createStore<AppEntity, AppState, AppEvents>({
9
9
  types: { Message },
10
10
  entities,
11
11
  middlewares,
@@ -1,6 +1,11 @@
1
- import "./vite-end"
1
+ import "./vite-env"
2
2
 
3
- import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"
3
+ import type {
4
+ BaseEntity,
5
+ EntitiesState,
6
+ EntityType,
7
+ EventsOf,
8
+ } from "@inglorious/store"
4
9
  import type { Api, TemplateResult } from "@inglorious/web"
5
10
 
6
11
  export interface MessageEntity extends BaseEntity {
@@ -17,7 +22,10 @@ export interface AppState extends EntitiesState<AppEntity> {
17
22
  message3: MessageEntity
18
23
  }
19
24
 
20
- export type AppApi = Api<AppEntity, AppState>
25
+ // The events the types below answer, so that `notify` checks them.
26
+ export type AppEvents = EventsOf<MessageType>
27
+
28
+ export type AppApi = Api<AppEntity, AppState, AppEvents>
21
29
 
22
30
  export type MessageType = {
23
31
  click: (entity: MessageEntity) => void
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import { Message } from "./types/message.vue"
2
+ import { Message } from "./message/index.vue"
3
3
  </script>
4
4
 
5
5
  <template>
@@ -1,7 +1,12 @@
1
1
  import { mount } from "@inglorious/web"
2
2
 
3
+ import type { AppApi } from "../types"
3
4
  // @ts-expect-error - Handled by @inglorious/vite-plugin-vue
4
- import { App } from "./app.vue"
5
+ import { App } from "./app/index.vue"
5
6
  import { store } from "./store"
6
7
 
7
- mount(store, (api) => App.render(null, api), document.getElementById("root")!)
8
+ mount(
9
+ store,
10
+ (api: AppApi) => App.render(null, api),
11
+ document.getElementById("root")!,
12
+ )
@@ -2,7 +2,7 @@ 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
5
- import { Message } from "../types/message.vue"
5
+ import { Message } from "../app/message/index.vue"
6
6
  import { entities } from "./entities"
7
7
  import { middlewares } from "./middlewares"
8
8
 
@@ -1,4 +1,4 @@
1
- import "./vite-end"
1
+ import "./vite-env"
2
2
 
3
3
  import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"
4
4
  import type { Api } from "@inglorious/web"
File without changes
File without changes