@inglorious/create-app 1.0.2 → 1.1.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.
- package/README.md +17 -0
- package/bin/index.js +289 -0
- package/package.json +2 -2
- package/templates/js/package.json +1 -0
- package/templates/js/src/store/index.js +1 -1
- package/templates/js/src/store/middlewares.js +1 -1
- package/templates/js-jsx/package.json +1 -0
- package/templates/js-jsx/src/store/index.js +1 -1
- package/templates/js-jsx/src/store/middlewares.js +1 -1
- package/templates/js-vue/package.json +1 -0
- package/templates/js-vue/src/store/index.js +1 -1
- package/templates/js-vue/src/store/middlewares.js +1 -1
- package/templates/minimal/index.html +2 -1
- package/templates/minimal/src/store/index.js +1 -1
- package/templates/minimal/src/store/middlewares.js +1 -1
- package/templates/ts/package.json +1 -0
- package/templates/ts/src/store/index.ts +1 -1
- package/templates/ts/src/store/middlewares.ts +2 -1
- package/templates/ts/types/index.d.ts +1 -1
- package/templates/ts-jsx/package.json +1 -0
- package/templates/ts-jsx/src/store/index.ts +1 -1
- package/templates/ts-jsx/src/store/middlewares.ts +2 -1
- package/templates/ts-jsx/types/index.d.ts +2 -1
- package/templates/ts-vue/package.json +1 -0
- package/templates/ts-vue/src/store/index.ts +1 -1
- package/templates/ts-vue/src/store/middlewares.ts +1 -1
- package/templates/ts-vue/types/index.d.ts +2 -1
package/README.md
CHANGED
|
@@ -23,6 +23,17 @@ pnpm create @inglorious/app
|
|
|
23
23
|
|
|
24
24
|
The CLI will guide you through the setup process, asking for a project name and which template you'd like to use.
|
|
25
25
|
|
|
26
|
+
For JavaScript and TypeScript Vite app templates, the CLI can also add optional app targets:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm create @inglorious/app@latest -- --pwa
|
|
30
|
+
npm create @inglorious/app@latest -- --mobile
|
|
31
|
+
npm create @inglorious/app@latest -- --pwa --mobile
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `--pwa` adds a web app manifest, service worker, and browser-only service worker registration.
|
|
35
|
+
- `--mobile` adds Capacitor dependencies, config, and mobile scripts for iOS and Android.
|
|
36
|
+
|
|
26
37
|
## Templates
|
|
27
38
|
|
|
28
39
|
The following templates are available to get you started:
|
|
@@ -50,6 +61,12 @@ pnpm install
|
|
|
50
61
|
pnpm dev
|
|
51
62
|
```
|
|
52
63
|
|
|
64
|
+
When mobile support is enabled, build and sync native projects with:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pnpm mobile:sync
|
|
68
|
+
```
|
|
69
|
+
|
|
53
70
|
## License
|
|
54
71
|
|
|
55
72
|
**MIT License - Free and open source**
|
package/bin/index.js
CHANGED
|
@@ -9,10 +9,19 @@ import prompts from "prompts"
|
|
|
9
9
|
import { fileURLToPath } from "url"
|
|
10
10
|
|
|
11
11
|
const INDENTATION = 2
|
|
12
|
+
const CAPACITOR_VERSION = "^8.5.0"
|
|
13
|
+
const AFTER_FIRST_CHARACTER_INDEX = 1
|
|
14
|
+
const EMPTY_LENGTH = 0
|
|
15
|
+
const FIRST_CHARACTER_INDEX = 0
|
|
16
|
+
const FIRST_ITEM_INDEX = 0
|
|
17
|
+
const NPM_VIEW_TIMEOUT_MS = 10_000
|
|
18
|
+
const USER_ARG_START_INDEX = 2
|
|
19
|
+
const VITE_APP_TEMPLATES = new Set(["js", "ts"])
|
|
12
20
|
|
|
13
21
|
async function main() {
|
|
14
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
15
23
|
const templatesRoot = path.join(__dirname, "../templates")
|
|
24
|
+
const cliOptions = parseCliOptions(process.argv.slice(USER_ARG_START_INDEX))
|
|
16
25
|
|
|
17
26
|
let canceled = false
|
|
18
27
|
|
|
@@ -71,6 +80,8 @@ async function main() {
|
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
let renderer = "lit-html"
|
|
83
|
+
let pwa = cliOptions.pwa === true
|
|
84
|
+
let mobile = cliOptions.mobile === true
|
|
74
85
|
|
|
75
86
|
// Ask about renderer for non-minimal templates
|
|
76
87
|
if (baseTemplate !== "minimal") {
|
|
@@ -108,6 +119,48 @@ async function main() {
|
|
|
108
119
|
renderer = selectedRenderer
|
|
109
120
|
}
|
|
110
121
|
|
|
122
|
+
const supportsAppFeatures = VITE_APP_TEMPLATES.has(baseTemplate)
|
|
123
|
+
|
|
124
|
+
if (supportsAppFeatures) {
|
|
125
|
+
const featureQuestions = []
|
|
126
|
+
|
|
127
|
+
if (cliOptions.pwa === null) {
|
|
128
|
+
featureQuestions.push({
|
|
129
|
+
type: "confirm",
|
|
130
|
+
name: "pwa",
|
|
131
|
+
message: "Enable PWA support?",
|
|
132
|
+
initial: false,
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (cliOptions.mobile === null) {
|
|
137
|
+
featureQuestions.push({
|
|
138
|
+
type: "confirm",
|
|
139
|
+
name: "mobile",
|
|
140
|
+
message: "Add hybrid mobile support with Capacitor?",
|
|
141
|
+
initial: false,
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (featureQuestions.length > EMPTY_LENGTH) {
|
|
146
|
+
const answers = await prompts(featureQuestions, { onCancel })
|
|
147
|
+
|
|
148
|
+
if (canceled) {
|
|
149
|
+
console.log("Operation canceled.")
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
pwa = cliOptions.pwa === true ? true : answers.pwa === true
|
|
154
|
+
mobile = cliOptions.mobile === true ? true : answers.mobile === true
|
|
155
|
+
}
|
|
156
|
+
} else if (pwa || mobile) {
|
|
157
|
+
console.warn(
|
|
158
|
+
"PWA and mobile options are only available for JavaScript and TypeScript Vite app templates.",
|
|
159
|
+
)
|
|
160
|
+
pwa = false
|
|
161
|
+
mobile = false
|
|
162
|
+
}
|
|
163
|
+
|
|
111
164
|
const spinner = ora(`Creating project "${projectName}"...`).start()
|
|
112
165
|
|
|
113
166
|
try {
|
|
@@ -143,6 +196,16 @@ async function main() {
|
|
|
143
196
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
|
|
144
197
|
pkg.name = projectName
|
|
145
198
|
|
|
199
|
+
if (pwa) {
|
|
200
|
+
spinner.text = "Adding PWA support..."
|
|
201
|
+
addPwaSupport(targetDir, projectName)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (mobile) {
|
|
205
|
+
spinner.text = "Adding Capacitor support..."
|
|
206
|
+
addMobileSupport(targetDir, pkg, projectName, templateName)
|
|
207
|
+
}
|
|
208
|
+
|
|
146
209
|
for (const depType of ["dependencies", "devDependencies"]) {
|
|
147
210
|
if (pkg[depType]) {
|
|
148
211
|
for (const [name, version] of Object.entries(pkg[depType])) {
|
|
@@ -195,6 +258,7 @@ async function main() {
|
|
|
195
258
|
const version = execSync(`npm view ${packageName} version`, {
|
|
196
259
|
encoding: "utf-8",
|
|
197
260
|
stdio: "pipe", // Prevent npm view output from cluttering the console
|
|
261
|
+
timeout: NPM_VIEW_TIMEOUT_MS,
|
|
198
262
|
}).trim()
|
|
199
263
|
return `^${version}`
|
|
200
264
|
} catch {
|
|
@@ -203,6 +267,231 @@ async function main() {
|
|
|
203
267
|
}
|
|
204
268
|
}
|
|
205
269
|
|
|
270
|
+
function parseCliOptions(args) {
|
|
271
|
+
return {
|
|
272
|
+
mobile: readBooleanFlag(args, "mobile"),
|
|
273
|
+
pwa: readBooleanFlag(args, "pwa"),
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function readBooleanFlag(args, name) {
|
|
278
|
+
if (args.includes(`--${name}`)) {
|
|
279
|
+
return true
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (args.includes(`--no-${name}`)) {
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return null
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function addPwaSupport(targetDir, projectName) {
|
|
290
|
+
const publicDir = path.join(targetDir, "public")
|
|
291
|
+
fs.mkdirSync(publicDir, { recursive: true })
|
|
292
|
+
fs.writeFileSync(path.join(publicDir, "sw.js"), createServiceWorkerSource())
|
|
293
|
+
fs.writeFileSync(
|
|
294
|
+
path.join(publicDir, "manifest.webmanifest"),
|
|
295
|
+
JSON.stringify(createWebManifest(projectName), null, INDENTATION) + EOL,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
injectPwaHeadTags(path.join(targetDir, "index.html"))
|
|
299
|
+
injectServiceWorkerRegistration(targetDir)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function addMobileSupport(targetDir, pkg, projectName, templateName) {
|
|
303
|
+
pkg.dependencies ??= {}
|
|
304
|
+
pkg.devDependencies ??= {}
|
|
305
|
+
pkg.scripts ??= {}
|
|
306
|
+
|
|
307
|
+
pkg.dependencies["@capacitor/core"] = CAPACITOR_VERSION
|
|
308
|
+
pkg.devDependencies["@capacitor/android"] = CAPACITOR_VERSION
|
|
309
|
+
pkg.devDependencies["@capacitor/cli"] = CAPACITOR_VERSION
|
|
310
|
+
pkg.devDependencies["@capacitor/ios"] = CAPACITOR_VERSION
|
|
311
|
+
|
|
312
|
+
pkg.scripts.cap = "cap"
|
|
313
|
+
pkg.scripts["mobile:sync"] = "pnpm build && cap sync"
|
|
314
|
+
pkg.scripts["mobile:ios"] = "pnpm build && cap run ios"
|
|
315
|
+
pkg.scripts["mobile:android"] = "pnpm build && cap run android"
|
|
316
|
+
|
|
317
|
+
const extension = templateName.startsWith("ts") ? "ts" : "js"
|
|
318
|
+
fs.writeFileSync(
|
|
319
|
+
path.join(targetDir, `capacitor.config.${extension}`),
|
|
320
|
+
createCapacitorConfigSource(projectName, extension),
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function createServiceWorkerSource() {
|
|
325
|
+
return `const CACHE_NAME = "inglorious-app-v1"
|
|
326
|
+
const APP_SHELL = ["/", "/index.html", "/logo.png", "/style.css"]
|
|
327
|
+
|
|
328
|
+
self.addEventListener("install", (event) => {
|
|
329
|
+
event.waitUntil(
|
|
330
|
+
caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL)),
|
|
331
|
+
)
|
|
332
|
+
self.skipWaiting()
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
self.addEventListener("activate", (event) => {
|
|
336
|
+
event.waitUntil(
|
|
337
|
+
caches
|
|
338
|
+
.keys()
|
|
339
|
+
.then((keys) =>
|
|
340
|
+
Promise.all(keys.map((key) => key === CACHE_NAME ? null : caches.delete(key))),
|
|
341
|
+
),
|
|
342
|
+
)
|
|
343
|
+
self.clients.claim()
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
self.addEventListener("fetch", (event) => {
|
|
347
|
+
if (event.request.method !== "GET") {
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
event.respondWith(
|
|
352
|
+
caches.match(event.request).then((cachedResponse) => {
|
|
353
|
+
if (cachedResponse) {
|
|
354
|
+
return cachedResponse
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return fetch(event.request).catch(() => {
|
|
358
|
+
if (event.request.mode === "navigate") {
|
|
359
|
+
return caches.match("/index.html")
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
throw new Error("Network request failed")
|
|
363
|
+
})
|
|
364
|
+
}),
|
|
365
|
+
)
|
|
366
|
+
})
|
|
367
|
+
`
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function createWebManifest(projectName) {
|
|
371
|
+
return {
|
|
372
|
+
name: projectName,
|
|
373
|
+
short_name: projectName,
|
|
374
|
+
description: "An Inglorious Web app.",
|
|
375
|
+
start_url: "/",
|
|
376
|
+
display: "standalone",
|
|
377
|
+
background_color: "#ffffff",
|
|
378
|
+
theme_color: "#111827",
|
|
379
|
+
icons: [
|
|
380
|
+
{
|
|
381
|
+
src: "/logo.png",
|
|
382
|
+
sizes: "192x192",
|
|
383
|
+
type: "image/png",
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
src: "/logo.png",
|
|
387
|
+
sizes: "512x512",
|
|
388
|
+
type: "image/png",
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function injectPwaHeadTags(indexPath) {
|
|
395
|
+
let index = fs.readFileSync(indexPath, "utf-8")
|
|
396
|
+
|
|
397
|
+
if (!index.includes('rel="manifest"')) {
|
|
398
|
+
index = index.replace(
|
|
399
|
+
/^([ \t]*<link rel="icon"[^>]*>\r?\n)/m,
|
|
400
|
+
`$1 <link rel="manifest" href="/manifest.webmanifest" />${EOL}`,
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!index.includes('name="theme-color"')) {
|
|
405
|
+
index = index.replace(
|
|
406
|
+
/^([ \t]*<meta name="viewport"[^>]*>\r?\n)/m,
|
|
407
|
+
`$1 <meta name="theme-color" content="#111827" />${EOL}`,
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
fs.writeFileSync(indexPath, index)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function injectServiceWorkerRegistration(targetDir) {
|
|
415
|
+
const mainPath = findMainPath(targetDir)
|
|
416
|
+
|
|
417
|
+
if (!mainPath) {
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
let main = fs.readFileSync(mainPath, "utf-8")
|
|
422
|
+
|
|
423
|
+
if (!main.includes("@inglorious/web/mobile")) {
|
|
424
|
+
main = main.replace(
|
|
425
|
+
/^([ \t]*import \{ mount \} from "@inglorious\/web"\r?\n)/m,
|
|
426
|
+
(match) =>
|
|
427
|
+
`${match}import { isNative } from "@inglorious/web/mobile"${EOL}`,
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (!main.includes('navigator.serviceWorker.register("/sw.js")')) {
|
|
432
|
+
main = `${main.trimEnd()}${EOL}${EOL}if (!isNative() && "serviceWorker" in navigator) {
|
|
433
|
+
navigator.serviceWorker.register("/sw.js")
|
|
434
|
+
}${EOL}`
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
fs.writeFileSync(mainPath, main)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function findMainPath(targetDir) {
|
|
441
|
+
const candidates = [
|
|
442
|
+
path.join(targetDir, "src/main.ts"),
|
|
443
|
+
path.join(targetDir, "src/main.js"),
|
|
444
|
+
]
|
|
445
|
+
|
|
446
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) ?? null
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function createCapacitorConfigSource(projectName, extension) {
|
|
450
|
+
const appId = `com.inglorious.${toJavaIdentifier(projectName)}`
|
|
451
|
+
|
|
452
|
+
if (extension === "ts") {
|
|
453
|
+
return `import type { CapacitorConfig } from "@capacitor/cli"
|
|
454
|
+
|
|
455
|
+
const config: CapacitorConfig = {
|
|
456
|
+
appId: "${appId}",
|
|
457
|
+
appName: "${projectName}",
|
|
458
|
+
webDir: "dist",
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export default config
|
|
462
|
+
`
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return `/** @type {import("@capacitor/cli").CapacitorConfig} */
|
|
466
|
+
const config = {
|
|
467
|
+
appId: "${appId}",
|
|
468
|
+
appName: "${projectName}",
|
|
469
|
+
webDir: "dist",
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export default config
|
|
473
|
+
`
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function toJavaIdentifier(value) {
|
|
477
|
+
const identifier = value
|
|
478
|
+
.toLowerCase()
|
|
479
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
480
|
+
.trim()
|
|
481
|
+
.split(" ")
|
|
482
|
+
.filter(Boolean)
|
|
483
|
+
.map((part, index) =>
|
|
484
|
+
index === FIRST_ITEM_INDEX
|
|
485
|
+
? part
|
|
486
|
+
: part.charAt(FIRST_CHARACTER_INDEX).toUpperCase() +
|
|
487
|
+
part.slice(AFTER_FIRST_CHARACTER_INDEX),
|
|
488
|
+
)
|
|
489
|
+
.join("")
|
|
490
|
+
.replace(/^[^a-z]+/, "")
|
|
491
|
+
|
|
492
|
+
return identifier || "app"
|
|
493
|
+
}
|
|
494
|
+
|
|
206
495
|
main().catch((e) => {
|
|
207
496
|
console.error(e)
|
|
208
497
|
})
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/create-app",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A scaffolding tool to quickly create a new app with Inglorious Web.",
|
|
5
|
-
"author": "
|
|
5
|
+
"author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"@inglorious/utils/": "https://unpkg.com/@inglorious%2Futils@latest/src/",
|
|
25
25
|
"@inglorious/store": "https://unpkg.com/@inglorious%2Fstore@latest/src/store.js",
|
|
26
26
|
"@inglorious/store/": "https://unpkg.com/@inglorious%2Fstore@latest/src/",
|
|
27
|
-
"@inglorious/web": "https://unpkg.com/@inglorious%2Fweb@latest/src/index.js"
|
|
27
|
+
"@inglorious/web": "https://unpkg.com/@inglorious%2Fweb@latest/src/index.js",
|
|
28
|
+
"@inglorious/web/": "https://unpkg.com/@inglorious%2Fweb@latest/src/"
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
</script>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "./vite-end"
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { BaseEntity, EntitiesState } from "@inglorious/store"
|
|
4
|
+
import { Api, TemplateResult } from "@inglorious/web"
|
|
4
5
|
|
|
5
6
|
// Define your entity types
|
|
6
7
|
export interface MessageEntity extends BaseEntity {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "./vite-end"
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { BaseEntity, EntitiesState } from "@inglorious/store"
|
|
4
|
+
import { Api, TemplateResult } from "@inglorious/web"
|
|
4
5
|
|
|
5
6
|
// Define your entity types
|
|
6
7
|
export interface MessageEntity extends BaseEntity {
|