@inglorious/create-app 1.7.1 → 1.8.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.
- package/README.md +6 -2
- package/bin/index.js +19 -4
- package/bin/lib/app-jsx.js +0 -11
- package/bin/lib/app-lit.js +0 -5
- package/bin/lib/app-vue.js +0 -6
- package/bin/lib/app.js +121 -28
- package/bin/lib/cli.js +2 -2
- package/bin/lib/constants.js +3 -2
- package/bin/lib/features.js +103 -6
- package/bin/lib/firebase.js +177 -0
- package/bin/lib/imports.js +0 -9
- package/bin/lib/mobile.js +0 -5
- package/bin/lib/prompts.js +311 -0
- package/bin/lib/pwa.js +21 -16
- package/bin/lib/questions.js +29 -7
- package/bin/lib/skills.js +11 -2
- package/bin/lib/source.js +4 -11
- package/package.json +1 -1
- package/templates/features/analytics/scripts/analytics-off-native.mjs +31 -0
- package/templates/features/push/public/sw-push.js +23 -0
- package/templates/features/push/scripts/push-vault-secrets.mjs +60 -0
- package/templates/features/push/supabase/functions/deno.json +6 -0
- package/templates/features/push/supabase/functions/push-notify/index.ts +187 -0
- package/templates/features/push/supabase/migrations/push.sql +115 -0
- package/templates/features/push/supabase/seed.sql +20 -0
- package/templates/js/package.json +2 -2
- package/templates/js/vite.config.js +0 -1
- package/templates/js-jsx/package.json +2 -2
- package/templates/js-jsx/vite.config.js +0 -1
- package/templates/js-vue/package.json +2 -2
- package/templates/js-vue/vite.config.js +0 -1
- package/templates/ssx-js/package.json +2 -2
- package/templates/ssx-js/src/site.config.js +0 -1
- package/templates/ssx-js-jsx/package.json +2 -2
- package/templates/ssx-js-jsx/src/site.config.js +0 -1
- package/templates/ssx-js-vue/package.json +2 -2
- package/templates/ssx-js-vue/src/pages/about.js +0 -1
- package/templates/ssx-js-vue/src/pages/index.js +0 -1
- package/templates/ssx-js-vue/src/site.config.js +0 -1
- package/templates/ssx-ts/package.json +2 -2
- package/templates/ssx-ts/src/site.config.ts +0 -1
- package/templates/ssx-ts/types/index.d.ts +0 -7
- package/templates/ssx-ts-jsx/package.json +2 -2
- package/templates/ssx-ts-jsx/src/site.config.ts +0 -1
- package/templates/ssx-ts-jsx/types/index.d.ts +0 -7
- package/templates/ssx-ts-vue/package.json +2 -2
- package/templates/ssx-ts-vue/src/pages/about.ts +0 -1
- package/templates/ssx-ts-vue/src/pages/index.ts +0 -1
- package/templates/ssx-ts-vue/src/site.config.ts +0 -1
- package/templates/ssx-ts-vue/types/index.d.ts +0 -6
- package/templates/ts/package.json +3 -3
- package/templates/ts/types/index.d.ts +0 -7
- package/templates/ts/vite.config.ts +0 -2
- package/templates/ts-jsx/package.json +3 -3
- package/templates/ts-jsx/types/index.d.ts +0 -7
- package/templates/ts-jsx/vite.config.ts +0 -2
- package/templates/ts-vue/package.json +3 -3
- package/templates/ts-vue/types/index.d.ts +0 -6
- package/templates/ts-vue/vite.config.ts +0 -2
package/README.md
CHANGED
|
@@ -34,9 +34,11 @@ For the JavaScript and TypeScript Vite templates, the CLI then asks about the ap
|
|
|
34
34
|
| Animate page transitions? | `--transitions` / `--no-transitions` | View transitions between pages (with routing and Inglorious UI) |
|
|
35
35
|
| Which icon packs? | `--icons=material,fontawesome,lucide` / `--no-icons` | Iconify icon sets, drawn by Inglorious UI's `Icon` (with Inglorious UI) |
|
|
36
36
|
| Translate the app? | `--i18n` / `--no-i18n`, `--languages=en,it` | `@inglorious/i18n`, a catalogue per language and a language picker; the first language is the default |
|
|
37
|
+
| Add analytics? | `--analytics` / `--no-analytics` | Google Analytics for Firebase behind a consent prompt, on the web and in the native apps |
|
|
38
|
+
| Add push notifications? | `--push` / `--no-push` | Firebase Cloud Messaging, a permission prompt and the Supabase side of sending; turns on PWA support |
|
|
37
39
|
| Enable PWA support? | `--pwa` / `--no-pwa` | `vite-plugin-pwa`, a manifest, icons, and a service worker that keeps installed apps on the latest version |
|
|
38
40
|
|
|
39
|
-
Every template except `minimal` then asks whether to add agent skills (`--skills` / `--no-skills`). Saying yes runs the [skills CLI](https://skills.sh) in the new project. It asks which coding agents to install for, and installs the [Inglorious Forge skills](https://github.com/IngloriousCoderz/forge-skills) that match your choices: `forge`, `forge-best-practices`, `store`, `web` and `web-best-practices` always, plus `ssx`, `vite-plugin-jsx` or `vite-plugin-vue`, `ui` and `ui-best-practices`, and `
|
|
41
|
+
Every template except `minimal` then asks whether to add agent skills (`--skills` / `--no-skills`). Saying yes runs the [skills CLI](https://skills.sh) in the new project. It asks which coding agents to install for, and installs the [Inglorious Forge skills](https://github.com/IngloriousCoderz/forge-skills) that match your choices: `forge`, `forge-best-practices`, `store`, `web` and `web-best-practices` always, plus `ssx`, `vite-plugin-jsx` or `vite-plugin-vue`, `ui` and `ui-best-practices`, `i18n`, `firebase` and `supabase` when the project uses them. If the install fails, the CLI prints the command to retry it.
|
|
40
42
|
|
|
41
43
|
The app is generated in the syntax you chose: lit-html, JSX or Vue. Every question answered on the command line is skipped, so a project can be created without any prompt:
|
|
42
44
|
|
|
@@ -47,9 +49,11 @@ The app is generated in the syntax you chose: lit-html, JSX or Vue. Every questi
|
|
|
47
49
|
| Select a rendering syntax | `--syntax=lit-html\|jsx\|vue` |
|
|
48
50
|
|
|
49
51
|
```bash
|
|
50
|
-
npm create @inglorious/app@latest -- my-app --template=ts --syntax=jsx --ui --theme=material-3 --routing --transitions --icons=material --languages=en,it --no-mobile --no-pwa --skills
|
|
52
|
+
npm create @inglorious/app@latest -- my-app --template=ts --syntax=jsx --ui --theme=material-3 --routing --transitions --icons=material --languages=en,it --no-mobile --no-analytics --no-push --no-pwa --skills
|
|
51
53
|
```
|
|
52
54
|
|
|
55
|
+
Analytics and push generate their setup steps into the new project's README, and a `.env.example`. Push also generates a Supabase migration, the `push-notify` edge function and a service worker: a push is sent by inserting a row in `notifications`. It needs a Firebase project, a Supabase project and a sign-in screen, since the permission prompt appears once someone is signed in with Supabase Auth.
|
|
56
|
+
|
|
53
57
|
## Templates
|
|
54
58
|
|
|
55
59
|
The following templates are available to get you started:
|
package/bin/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "./lib/constants.js"
|
|
19
19
|
import { SYNTAXES, TEMPLATES } from "./lib/features.js"
|
|
20
20
|
import { copyDir } from "./lib/file.js"
|
|
21
|
+
import { addFirebaseSupport } from "./lib/firebase.js"
|
|
21
22
|
import { addMobileSupport } from "./lib/mobile.js"
|
|
22
23
|
import { addPwaSupport } from "./lib/pwa.js"
|
|
23
24
|
import { askAppFeatures } from "./lib/questions.js"
|
|
@@ -93,6 +94,8 @@ async function main() {
|
|
|
93
94
|
languages: [],
|
|
94
95
|
mobile: false,
|
|
95
96
|
pwa: false,
|
|
97
|
+
analytics: false,
|
|
98
|
+
push: false,
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
if (VITE_APP_TEMPLATES.has(baseTemplate)) {
|
|
@@ -102,9 +105,14 @@ async function main() {
|
|
|
102
105
|
console.log("Operation canceled.")
|
|
103
106
|
return
|
|
104
107
|
}
|
|
105
|
-
} else if (
|
|
108
|
+
} else if (
|
|
109
|
+
cliOptions.pwa ||
|
|
110
|
+
cliOptions.mobile ||
|
|
111
|
+
cliOptions.analytics ||
|
|
112
|
+
cliOptions.push
|
|
113
|
+
) {
|
|
106
114
|
console.warn(
|
|
107
|
-
"PWA and
|
|
115
|
+
"PWA, mobile, analytics and push options are only available for JavaScript and TypeScript Vite app templates.",
|
|
108
116
|
)
|
|
109
117
|
}
|
|
110
118
|
|
|
@@ -167,7 +175,9 @@ async function main() {
|
|
|
167
175
|
features.ui ||
|
|
168
176
|
features.routing ||
|
|
169
177
|
Boolean(features.icons.length) ||
|
|
170
|
-
Boolean(features.languages.length)
|
|
178
|
+
Boolean(features.languages.length) ||
|
|
179
|
+
features.analytics ||
|
|
180
|
+
features.push
|
|
171
181
|
|
|
172
182
|
if (hasAppFeatures) {
|
|
173
183
|
spinner.text = "Adding app features..."
|
|
@@ -181,7 +191,7 @@ async function main() {
|
|
|
181
191
|
|
|
182
192
|
if (features.pwa) {
|
|
183
193
|
spinner.text = "Adding PWA support..."
|
|
184
|
-
addPwaSupport(targetDir, pkg, projectName)
|
|
194
|
+
addPwaSupport(targetDir, pkg, projectName, { push: features.push })
|
|
185
195
|
}
|
|
186
196
|
|
|
187
197
|
if (features.mobile) {
|
|
@@ -189,6 +199,11 @@ async function main() {
|
|
|
189
199
|
addMobileSupport(targetDir, pkg, projectName)
|
|
190
200
|
}
|
|
191
201
|
|
|
202
|
+
if (features.analytics || features.push) {
|
|
203
|
+
spinner.text = "Adding Firebase support..."
|
|
204
|
+
addFirebaseSupport(targetDir, pkg, { ...features, templatesRoot })
|
|
205
|
+
}
|
|
206
|
+
|
|
192
207
|
for (const depType of ["dependencies", "devDependencies"]) {
|
|
193
208
|
if (pkg[depType]) {
|
|
194
209
|
for (const [name, version] of Object.entries(pkg[depType])) {
|
package/bin/lib/app-jsx.js
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
// The app's components in JSX: .jsx or .tsx files, compiled to lit-html by
|
|
2
|
-
// @inglorious/vite-plugin-jsx. A capitalized tag with props renders a
|
|
3
|
-
// component; `<Message entityId="message1" />` renders an entity. TypeScript
|
|
4
|
-
// expects components to be functions, so in .tsx each capitalized tag is
|
|
5
|
-
// preceded by a `@ts-expect-error`.
|
|
6
|
-
|
|
7
1
|
import { renderLocals, selectChange } from "./app-lit.js"
|
|
8
2
|
import { importBlock } from "./imports.js"
|
|
9
3
|
|
|
@@ -152,21 +146,16 @@ ${span}
|
|
|
152
146
|
`
|
|
153
147
|
}
|
|
154
148
|
|
|
155
|
-
// The comment line before a capitalized tag in .tsx: `{/* */}` among JSX
|
|
156
|
-
// children, `//` inside code.
|
|
157
149
|
function expectError(app, indent, isChild = true) {
|
|
158
150
|
if (!app.isTypeScript) return ""
|
|
159
151
|
const comment = isChild ? `{/* ${EXPECT_ERROR} */}` : `// ${EXPECT_ERROR}`
|
|
160
152
|
return `${indent}${comment}\n`
|
|
161
153
|
}
|
|
162
154
|
|
|
163
|
-
// A callback's parameter. The components' props are not typed in .tsx, so
|
|
164
|
-
// TypeScript cannot infer it.
|
|
165
155
|
function typed(app, name, type) {
|
|
166
156
|
return app.isTypeScript ? `${name}: ${type}` : name
|
|
167
157
|
}
|
|
168
158
|
|
|
169
|
-
// A page's label: translated, or the English text.
|
|
170
159
|
function labelText(app, page) {
|
|
171
160
|
return app.i18n ? `{t("${page.key}")}` : page.text
|
|
172
161
|
}
|
package/bin/lib/app-lit.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// The app's components in lit-html: tagged templates in .js or .ts files.
|
|
2
|
-
|
|
3
1
|
import { importBlock } from "./imports.js"
|
|
4
2
|
|
|
5
3
|
export const extension = (app) => (app.isTypeScript ? "ts" : "js")
|
|
@@ -53,7 +51,6 @@ ${template}
|
|
|
53
51
|
`
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
// The app's own entities are always there; TypeScript cannot know that.
|
|
57
54
|
export function renderLocals(app) {
|
|
58
55
|
const always = app.isTypeScript ? "!" : ""
|
|
59
56
|
const locals = []
|
|
@@ -64,8 +61,6 @@ export function renderLocals(app) {
|
|
|
64
61
|
return locals.length ? `${locals.join("\n")}\n\n` : ""
|
|
65
62
|
}
|
|
66
63
|
|
|
67
|
-
// The native language picker's change handler, laid out as Prettier would at
|
|
68
|
-
// `indent`. TypeScript has to be told what the event's target is.
|
|
69
64
|
export function selectChange(app, indent) {
|
|
70
65
|
if (!app.isTypeScript) {
|
|
71
66
|
return `(event) =>
|
package/bin/lib/app-vue.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// The app's components as Vue single-file components, compiled to lit-html by
|
|
2
|
-
// @inglorious/vite-plugin-vue. Imported Inglorious UI components are tags with
|
|
3
|
-
// props; imported .vue components with `v-bind="id"` render store entities;
|
|
4
|
-
// script values that read `api` are computed on every render.
|
|
5
|
-
|
|
6
1
|
import { importBlock } from "./imports.js"
|
|
7
2
|
import { LINE_WIDTH } from "./source.js"
|
|
8
3
|
|
|
@@ -173,7 +168,6 @@ ${nav.join("\n")}
|
|
|
173
168
|
|
|
174
169
|
if (!app.tabs) return header
|
|
175
170
|
|
|
176
|
-
// Each action on one line when it fits, one property per line otherwise.
|
|
177
171
|
const actions = pages.map((page) => {
|
|
178
172
|
const fields = [
|
|
179
173
|
`value: '${page.path}'`,
|
package/bin/lib/app.js
CHANGED
|
@@ -5,9 +5,16 @@ import path from "path"
|
|
|
5
5
|
import * as jsxComponents from "./app-jsx.js"
|
|
6
6
|
import * as litComponents from "./app-lit.js"
|
|
7
7
|
import * as vueComponents from "./app-vue.js"
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
CAPACITOR_FIREBASE_VERSION,
|
|
10
|
+
CAPACITOR_VERSION,
|
|
11
|
+
FIREBASE_VERSION,
|
|
12
|
+
INDENTATION,
|
|
13
|
+
SUPABASE_VERSION,
|
|
14
|
+
} from "./constants.js"
|
|
15
|
+
import { ICON_PACKS, LANGUAGES, PROMPT_TEXTS } from "./features.js"
|
|
10
16
|
import { importBlock } from "./imports.js"
|
|
17
|
+
import { envExample, promptFiles } from "./prompts.js"
|
|
11
18
|
import { appModel, call, LINE_WIDTH, union } from "./source.js"
|
|
12
19
|
|
|
13
20
|
const COMPONENTS = {
|
|
@@ -18,8 +25,8 @@ const COMPONENTS = {
|
|
|
18
25
|
|
|
19
26
|
/**
|
|
20
27
|
* Writes the source of an app with the features chosen: Inglorious UI,
|
|
21
|
-
* routing, page transitions, icon packs, internationalization
|
|
22
|
-
* layout, in lit-html, JSX or Vue. It replaces the template's hello world,
|
|
28
|
+
* routing, page transitions, icon packs, internationalization, a mobile
|
|
29
|
+
* layout, analytics and push prompts, in lit-html, JSX or Vue. It replaces the template's hello world,
|
|
23
30
|
* keeping its message type as the example on the home page. The files come
|
|
24
31
|
* out as Prettier and the template's ESLint config would leave them.
|
|
25
32
|
*
|
|
@@ -36,14 +43,16 @@ const COMPONENTS = {
|
|
|
36
43
|
* @param {boolean} options.transitions
|
|
37
44
|
* @param {string[]} options.icons - Keys of ICON_PACKS.
|
|
38
45
|
* @param {string[]} options.languages - Keys of LANGUAGES, the default first.
|
|
46
|
+
* @param {boolean} options.analytics
|
|
47
|
+
* @param {boolean} options.push
|
|
39
48
|
*/
|
|
40
49
|
export function addAppFeatures(targetDir, pkg, options) {
|
|
41
50
|
const app = { ...options, ext: options.isTypeScript ? "ts" : "js" }
|
|
42
51
|
app.i18n = Boolean(app.languages.length)
|
|
43
|
-
// On a phone the pages are tabs at the bottom; elsewhere, links on top.
|
|
44
52
|
app.tabs = app.ui && app.routing && app.mobile
|
|
45
53
|
app.links = app.routing && !app.tabs
|
|
46
54
|
app.isVue = app.renderer === "vue"
|
|
55
|
+
app.hasPrompts = app.analytics || app.push
|
|
47
56
|
|
|
48
57
|
const components = COMPONENTS[app.renderer]
|
|
49
58
|
const componentExt = components.extension(app)
|
|
@@ -68,9 +77,13 @@ export function addAppFeatures(targetDir, pkg, options) {
|
|
|
68
77
|
files[`src/i18n/index.${app.ext}`] = i18nSource(app)
|
|
69
78
|
for (const language of app.languages) {
|
|
70
79
|
files[`src/i18n/locales/${language}.json`] =
|
|
71
|
-
JSON.stringify(
|
|
80
|
+
JSON.stringify(catalogue(app, language), null, INDENTATION) + EOL
|
|
72
81
|
}
|
|
73
82
|
}
|
|
83
|
+
if (app.hasPrompts) {
|
|
84
|
+
Object.assign(files, promptFiles(app))
|
|
85
|
+
files[".env.example"] = envExample(app)
|
|
86
|
+
}
|
|
74
87
|
if (app.isTypeScript) {
|
|
75
88
|
files["types/index.d.ts"] = typesSource(app)
|
|
76
89
|
}
|
|
@@ -91,9 +104,31 @@ function addDependencies(pkg, app) {
|
|
|
91
104
|
for (const key of app.icons) {
|
|
92
105
|
Object.assign(pkg.dependencies, ICON_PACKS[key].packages)
|
|
93
106
|
}
|
|
107
|
+
if (app.hasPrompts) {
|
|
108
|
+
pkg.dependencies["@inglorious/firebase"] = "workspace:*"
|
|
109
|
+
pkg.dependencies["@capacitor/core"] = CAPACITOR_VERSION
|
|
110
|
+
pkg.dependencies.firebase = FIREBASE_VERSION
|
|
111
|
+
}
|
|
112
|
+
if (app.analytics) {
|
|
113
|
+
pkg.dependencies["@capacitor-firebase/analytics"] =
|
|
114
|
+
CAPACITOR_FIREBASE_VERSION
|
|
115
|
+
}
|
|
116
|
+
if (app.push) {
|
|
117
|
+
pkg.dependencies["@capacitor-firebase/messaging"] =
|
|
118
|
+
CAPACITOR_FIREBASE_VERSION
|
|
119
|
+
pkg.dependencies["@supabase/supabase-js"] = SUPABASE_VERSION
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function catalogue(app, language) {
|
|
124
|
+
const { analytics, push } = PROMPT_TEXTS[language]
|
|
125
|
+
return {
|
|
126
|
+
...LANGUAGES[language].texts,
|
|
127
|
+
...(app.analytics && { analytics }),
|
|
128
|
+
...(app.push && { push }),
|
|
129
|
+
}
|
|
94
130
|
}
|
|
95
131
|
|
|
96
|
-
// The theme is a class on <body>, which the theme's stylesheet is scoped to.
|
|
97
132
|
function applyTheme(targetDir, app) {
|
|
98
133
|
const htmlPath = path.join(targetDir, "index.html")
|
|
99
134
|
if (!fs.existsSync(htmlPath)) return
|
|
@@ -114,8 +149,6 @@ function componentImport(app, names, from) {
|
|
|
114
149
|
: line
|
|
115
150
|
}
|
|
116
151
|
|
|
117
|
-
// ---------------------------------------------------------------- main
|
|
118
|
-
|
|
119
152
|
function mainSource(app) {
|
|
120
153
|
const imports = []
|
|
121
154
|
if (app.ui) {
|
|
@@ -130,15 +163,21 @@ function mainSource(app) {
|
|
|
130
163
|
}
|
|
131
164
|
imports.push('import "./app.css"')
|
|
132
165
|
imports.push(
|
|
133
|
-
|
|
166
|
+
`import { ${app.hasPrompts ? "html, mount" : "mount"} } from "@inglorious/web"`,
|
|
134
167
|
componentImport(app, "App", "./app"),
|
|
135
168
|
'import { store } from "./store"',
|
|
136
169
|
)
|
|
137
170
|
if (app.i18n) imports.push('import { ready } from "./i18n"')
|
|
171
|
+
if (app.push) imports.push('import { supabase } from "./api/supabase"')
|
|
172
|
+
if (app.hasPrompts) {
|
|
173
|
+
imports.push('import { renderPrompts } from "./common/prompt"')
|
|
174
|
+
}
|
|
138
175
|
|
|
139
176
|
const args = [
|
|
140
177
|
"store",
|
|
141
|
-
|
|
178
|
+
app.hasPrompts
|
|
179
|
+
? "(api) => html`${App.render(null, api)} ${renderPrompts(api)}`"
|
|
180
|
+
: "(api) => App.render(null, api)",
|
|
142
181
|
app.isTypeScript
|
|
143
182
|
? 'document.getElementById("root")!'
|
|
144
183
|
: 'document.getElementById("root")',
|
|
@@ -152,22 +191,56 @@ ${call("mount", args, " ", ",")}
|
|
|
152
191
|
)`
|
|
153
192
|
: call("mount", args, "", "")
|
|
154
193
|
|
|
194
|
+
const session = app.push
|
|
195
|
+
? `
|
|
196
|
+
|
|
197
|
+
supabase.auth.onAuthStateChange((_event, session) => {
|
|
198
|
+
store.notify(session ? "#pushPrompt:pushStart" : "#pushPrompt:pushStop")
|
|
199
|
+
})`
|
|
200
|
+
: ""
|
|
201
|
+
|
|
155
202
|
return `${importBlock(imports)}
|
|
156
203
|
|
|
157
|
-
${body}
|
|
204
|
+
${body}${session}
|
|
158
205
|
`
|
|
159
206
|
}
|
|
160
207
|
|
|
161
|
-
// The Inglorious UI components the app uses, for their stylesheets.
|
|
162
208
|
function uiComponents(app) {
|
|
163
209
|
const names = ["app-bar", "container"]
|
|
164
210
|
if (app.links) names.push("link")
|
|
165
211
|
if (app.tabs) names.push("bottom-navigation")
|
|
166
212
|
if (app.icons.length) names.push("icon")
|
|
167
213
|
if (app.i18n) names.push("select")
|
|
214
|
+
if (app.hasPrompts) names.push("snackbar", "button", "flex")
|
|
168
215
|
return names
|
|
169
216
|
}
|
|
170
217
|
|
|
218
|
+
const PROMPT_ABOVE_TABS = `
|
|
219
|
+
body {
|
|
220
|
+
--iw-snackbar-offset-bottom: 5rem;
|
|
221
|
+
}
|
|
222
|
+
`
|
|
223
|
+
|
|
224
|
+
const PLAIN_PROMPT = `
|
|
225
|
+
.app-prompt {
|
|
226
|
+
position: fixed;
|
|
227
|
+
inset-block-end: 1rem;
|
|
228
|
+
inset-inline: 1rem;
|
|
229
|
+
display: flex;
|
|
230
|
+
flex-wrap: wrap;
|
|
231
|
+
align-items: center;
|
|
232
|
+
gap: 0.5rem;
|
|
233
|
+
padding: 0.75rem 1rem;
|
|
234
|
+
border: 1px solid #ddd;
|
|
235
|
+
background: #fff;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.app-prompt p {
|
|
239
|
+
flex: 1 1 100%;
|
|
240
|
+
margin: 0;
|
|
241
|
+
}
|
|
242
|
+
`
|
|
243
|
+
|
|
171
244
|
function appStyles(app) {
|
|
172
245
|
const layout = `body {
|
|
173
246
|
min-height: 100dvh;
|
|
@@ -191,10 +264,8 @@ main {
|
|
|
191
264
|
align-items: center;
|
|
192
265
|
gap: 1rem;
|
|
193
266
|
}
|
|
194
|
-
`
|
|
267
|
+
${app.tabs && app.hasPrompts ? PROMPT_ABOVE_TABS : ""}`
|
|
195
268
|
if (app.ui) {
|
|
196
|
-
// In Vue the navigation is the app bar's content rather than its trailing
|
|
197
|
-
// slot, so it pushes itself to the end.
|
|
198
269
|
return app.isVue
|
|
199
270
|
? `${layout}
|
|
200
271
|
.iw-app-bar .app-nav {
|
|
@@ -203,7 +274,7 @@ main {
|
|
|
203
274
|
`
|
|
204
275
|
: layout
|
|
205
276
|
}
|
|
206
|
-
return `${layout}
|
|
277
|
+
return `${layout}${app.hasPrompts ? PLAIN_PROMPT : ""}
|
|
207
278
|
header {
|
|
208
279
|
display: flex;
|
|
209
280
|
align-items: center;
|
|
@@ -218,8 +289,6 @@ main {
|
|
|
218
289
|
`
|
|
219
290
|
}
|
|
220
291
|
|
|
221
|
-
// ---------------------------------------------------------------- store
|
|
222
|
-
|
|
223
292
|
function storeSource(app) {
|
|
224
293
|
const imports = [
|
|
225
294
|
'import { createStore } from "@inglorious/store"',
|
|
@@ -244,10 +313,18 @@ function storeSource(app) {
|
|
|
244
313
|
imports.push('import type { AppEntity, AppState } from "../../types"')
|
|
245
314
|
}
|
|
246
315
|
if (app.i18n) imports.push('import { I18n } from "../i18n"')
|
|
316
|
+
if (app.analytics) {
|
|
317
|
+
imports.push('import { Analytics } from "../types/analytics"')
|
|
318
|
+
}
|
|
319
|
+
if (app.push) {
|
|
320
|
+
imports.push('import { PushPrompt } from "../types/push-prompt"')
|
|
321
|
+
}
|
|
247
322
|
|
|
248
323
|
const types = ["Message", "Home"]
|
|
249
324
|
if (app.routing) types.push("About", "Router")
|
|
250
325
|
if (app.i18n) types.push("I18n")
|
|
326
|
+
if (app.analytics) types.push("Analytics")
|
|
327
|
+
if (app.push) types.push("PushPrompt")
|
|
251
328
|
const oneLine = ` types: { ${types.join(", ")} },`
|
|
252
329
|
const typesValue =
|
|
253
330
|
oneLine.length <= LINE_WIDTH
|
|
@@ -269,17 +346,15 @@ ${typesValue}
|
|
|
269
346
|
`
|
|
270
347
|
}
|
|
271
348
|
|
|
272
|
-
// The entities the store creates on its own, as the comment in the store
|
|
273
|
-
// lists them.
|
|
274
349
|
function singletons(app) {
|
|
275
350
|
const ids = ["home"]
|
|
276
351
|
if (app.routing) ids.push("about", "router")
|
|
277
352
|
if (app.i18n) ids.push("i18n")
|
|
353
|
+
if (app.analytics) ids.push("analytics")
|
|
354
|
+
if (app.push) ids.push("pushPrompt")
|
|
278
355
|
return ids
|
|
279
356
|
}
|
|
280
357
|
|
|
281
|
-
// Only the entities that need data, or come more than one to a type; the
|
|
282
|
-
// store creates the rest.
|
|
283
358
|
function entitiesSource(app) {
|
|
284
359
|
const head = app.isTypeScript
|
|
285
360
|
? `import type { AppState } from "../../types"
|
|
@@ -309,8 +384,6 @@ export const routes = {`
|
|
|
309
384
|
`
|
|
310
385
|
}
|
|
311
386
|
|
|
312
|
-
// ---------------------------------------------------------------- i18n
|
|
313
|
-
|
|
314
387
|
function i18nSource(app) {
|
|
315
388
|
const [defaultLocale] = app.languages
|
|
316
389
|
return `import { createI18n } from "@inglorious/i18n"
|
|
@@ -325,12 +398,10 @@ export const { I18n, t, ready } = createI18n({
|
|
|
325
398
|
`
|
|
326
399
|
}
|
|
327
400
|
|
|
328
|
-
// ---------------------------------------------------------------- types
|
|
329
|
-
|
|
330
401
|
function typesSource(app) {
|
|
331
402
|
const imports = [
|
|
332
403
|
'import "./vite-end"',
|
|
333
|
-
'import type {
|
|
404
|
+
'import type { BaseEntity, EntitiesState, EntityType } from "@inglorious/store"',
|
|
334
405
|
'import type { Api, TemplateResult } from "@inglorious/web"',
|
|
335
406
|
]
|
|
336
407
|
const entities = ["MessageEntity", "HomeEntity"]
|
|
@@ -375,6 +446,28 @@ function typesSource(app) {
|
|
|
375
446
|
)
|
|
376
447
|
state.unshift(" i18n: AppI18nEntity")
|
|
377
448
|
}
|
|
449
|
+
if (app.analytics) {
|
|
450
|
+
imports.push(
|
|
451
|
+
'import type { AnalyticsEntity } from "@inglorious/web/analytics"',
|
|
452
|
+
)
|
|
453
|
+
entities.push("AppAnalyticsEntity")
|
|
454
|
+
declarations.push(
|
|
455
|
+
`export interface AppAnalyticsEntity extends AnalyticsEntity {
|
|
456
|
+
type: "Analytics"
|
|
457
|
+
}`,
|
|
458
|
+
)
|
|
459
|
+
state.push(" analytics: AppAnalyticsEntity")
|
|
460
|
+
}
|
|
461
|
+
if (app.push) {
|
|
462
|
+
imports.push('import type { PushPromptEntity } from "@inglorious/web/push"')
|
|
463
|
+
entities.push("AppPushPromptEntity")
|
|
464
|
+
declarations.push(
|
|
465
|
+
`export interface AppPushPromptEntity extends PushPromptEntity {
|
|
466
|
+
type: "PushPrompt"
|
|
467
|
+
}`,
|
|
468
|
+
)
|
|
469
|
+
state.push(" pushPrompt: AppPushPromptEntity")
|
|
470
|
+
}
|
|
378
471
|
|
|
379
472
|
return `${importBlock(imports)}
|
|
380
473
|
|
package/bin/lib/cli.js
CHANGED
|
@@ -5,6 +5,8 @@ export function parseCliOptions(args) {
|
|
|
5
5
|
syntax: readValueFlag(args, "syntax"),
|
|
6
6
|
mobile: readBooleanFlag(args, "mobile"),
|
|
7
7
|
pwa: readBooleanFlag(args, "pwa"),
|
|
8
|
+
analytics: readBooleanFlag(args, "analytics"),
|
|
9
|
+
push: readBooleanFlag(args, "push"),
|
|
8
10
|
ui: readBooleanFlag(args, "ui"),
|
|
9
11
|
theme: readValueFlag(args, "theme"),
|
|
10
12
|
routing: readBooleanFlag(args, "routing"),
|
|
@@ -28,14 +30,12 @@ export function readBooleanFlag(args, name) {
|
|
|
28
30
|
return null
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
// `--name=value`, or null when the flag is not given.
|
|
32
33
|
export function readValueFlag(args, name) {
|
|
33
34
|
const prefix = `--${name}=`
|
|
34
35
|
const arg = args.find((value) => value.startsWith(prefix))
|
|
35
36
|
return arg ? arg.slice(prefix.length) : null
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
// `--name=a,b`, `--no-name` for an empty list, or null when neither is given.
|
|
39
39
|
export function readListFlag(args, name) {
|
|
40
40
|
if (args.includes(`--no-${name}`)) {
|
|
41
41
|
return []
|
package/bin/lib/constants.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export const CAPACITOR_VERSION = "^8.5.3"
|
|
2
|
+
export const CAPACITOR_FIREBASE_VERSION = "^8.5.2"
|
|
3
|
+
export const FIREBASE_VERSION = "^12.19.0"
|
|
4
|
+
export const SUPABASE_VERSION = "^2.118.0"
|
|
2
5
|
export const VITE_PLUGIN_PWA_VERSION = "^2.0.0"
|
|
3
6
|
export const VITE_PWA_ASSETS_GENERATOR_VERSION = "^2.0.0"
|
|
4
|
-
// TypeScript 7 has no JavaScript API, which typescript-eslint and the
|
|
5
|
-
// Capacitor CLI import, so `typescript` stays on 6.
|
|
6
7
|
export const TYPESCRIPT_VERSION = "npm:@typescript/typescript6@^6.0.2"
|
|
7
8
|
export const WORKBOX_WINDOW_VERSION = "^7.4.1"
|
|
8
9
|
|
package/bin/lib/features.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// The choices create-app offers for lit-html apps, and what each one brings.
|
|
2
|
-
|
|
3
1
|
export const TEMPLATES = {
|
|
4
2
|
minimal: {
|
|
5
3
|
title: "Minimal",
|
|
@@ -41,9 +39,6 @@ export const THEMES = {
|
|
|
41
39
|
"ios-26": { title: "iOS 26", description: "Liquid Glass inspired" },
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
// Icon sets from Iconify: one module per icon, so only the icons an app
|
|
45
|
-
// imports are bundled. `nav` names the icons for the two example pages,
|
|
46
|
-
// `showcase` the one shown on the home page.
|
|
47
42
|
export const ICON_PACKS = {
|
|
48
43
|
material: {
|
|
49
44
|
title: "Material Symbols",
|
|
@@ -93,7 +88,6 @@ export const ICON_PACKS = {
|
|
|
93
88
|
},
|
|
94
89
|
}
|
|
95
90
|
|
|
96
|
-
// The languages offered for the example app, with its few texts in each.
|
|
97
91
|
export const LANGUAGES = {
|
|
98
92
|
en: language("English", {
|
|
99
93
|
nav: { home: "Home", about: "About" },
|
|
@@ -168,6 +162,109 @@ export const LANGUAGES = {
|
|
|
168
162
|
}),
|
|
169
163
|
}
|
|
170
164
|
|
|
165
|
+
export const PROMPT_TEXTS = {
|
|
166
|
+
en: {
|
|
167
|
+
analytics: {
|
|
168
|
+
prompt:
|
|
169
|
+
"May we measure how you use this app? It helps us make it better.",
|
|
170
|
+
accept: "Allow",
|
|
171
|
+
decline: "No thanks",
|
|
172
|
+
},
|
|
173
|
+
push: {
|
|
174
|
+
prompt: "Turn on push notifications?",
|
|
175
|
+
ask: "Notify me",
|
|
176
|
+
decline: "Not now",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
it: {
|
|
180
|
+
analytics: {
|
|
181
|
+
prompt: "Possiamo misurare come usi questa app? Ci aiuta a migliorarla.",
|
|
182
|
+
accept: "Consenti",
|
|
183
|
+
decline: "No, grazie",
|
|
184
|
+
},
|
|
185
|
+
push: {
|
|
186
|
+
prompt: "Attivare le notifiche push?",
|
|
187
|
+
ask: "Avvisami",
|
|
188
|
+
decline: "Non ora",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
fr: {
|
|
192
|
+
analytics: {
|
|
193
|
+
prompt:
|
|
194
|
+
"Pouvons-nous mesurer comment vous utilisez cette app\u00a0? Cela nous aide à l'améliorer.",
|
|
195
|
+
accept: "Autoriser",
|
|
196
|
+
decline: "Non merci",
|
|
197
|
+
},
|
|
198
|
+
push: {
|
|
199
|
+
prompt: "Activer les notifications push\u00a0?",
|
|
200
|
+
ask: "Me prévenir",
|
|
201
|
+
decline: "Plus tard",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
de: {
|
|
205
|
+
analytics: {
|
|
206
|
+
prompt:
|
|
207
|
+
"Dürfen wir messen, wie du diese App nutzt? Das hilft uns, sie zu verbessern.",
|
|
208
|
+
accept: "Erlauben",
|
|
209
|
+
decline: "Nein, danke",
|
|
210
|
+
},
|
|
211
|
+
push: {
|
|
212
|
+
prompt: "Push-Benachrichtigungen aktivieren?",
|
|
213
|
+
ask: "Benachrichtigen",
|
|
214
|
+
decline: "Nicht jetzt",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
es: {
|
|
218
|
+
analytics: {
|
|
219
|
+
prompt: "¿Podemos medir cómo usas esta app? Nos ayuda a mejorarla.",
|
|
220
|
+
accept: "Permitir",
|
|
221
|
+
decline: "No, gracias",
|
|
222
|
+
},
|
|
223
|
+
push: {
|
|
224
|
+
prompt: "¿Activar las notificaciones push?",
|
|
225
|
+
ask: "Avisarme",
|
|
226
|
+
decline: "Ahora no",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
pt: {
|
|
230
|
+
analytics: {
|
|
231
|
+
prompt: "Podemos medir como usas esta app? Ajuda-nos a melhorá-la.",
|
|
232
|
+
accept: "Permitir",
|
|
233
|
+
decline: "Não, obrigado",
|
|
234
|
+
},
|
|
235
|
+
push: {
|
|
236
|
+
prompt: "Ativar as notificações push?",
|
|
237
|
+
ask: "Avisar-me",
|
|
238
|
+
decline: "Agora não",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
ja: {
|
|
242
|
+
analytics: {
|
|
243
|
+
prompt: "このアプリの使われ方を計測してもよいですか?改善に役立てます。",
|
|
244
|
+
accept: "許可する",
|
|
245
|
+
decline: "許可しない",
|
|
246
|
+
},
|
|
247
|
+
push: {
|
|
248
|
+
prompt: "プッシュ通知をオンにしますか?",
|
|
249
|
+
ask: "通知する",
|
|
250
|
+
decline: "今はしない",
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
ar: {
|
|
254
|
+
analytics: {
|
|
255
|
+
prompt:
|
|
256
|
+
"هل تسمح لنا بقياس طريقة استخدامك لهذا التطبيق؟ يساعدنا ذلك على تحسينه.",
|
|
257
|
+
accept: "سماح",
|
|
258
|
+
decline: "لا، شكرًا",
|
|
259
|
+
},
|
|
260
|
+
push: {
|
|
261
|
+
prompt: "هل تريد تفعيل الإشعارات الفورية؟",
|
|
262
|
+
ask: "أعلمني",
|
|
263
|
+
decline: "ليس الآن",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
|
|
171
268
|
function from(name, source) {
|
|
172
269
|
return { name, from: source }
|
|
173
270
|
}
|