@cavulsqa/create 2.8.2 → 2.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/scaffold.mjs +8 -21
- package/lib/templateFiles.mjs +62 -0
- package/package.json +2 -2
- package/templates/f7-app/src/app/storage.config.ts +10 -1
- package/templates/f7-app/src/locales/en.json +0 -1
- package/templates/f7-app/src/locales/fr.json +0 -1
- package/templates/f7-app/src/modules/settings/views/SettingsView.vue +1 -1
- package/templates/f7-app/src/shared/database/database.ts +9 -1
- package/templates/f7-app/vite.config.ts +9 -1
package/lib/scaffold.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { listTemplateFiles } from "./templateFiles.mjs";
|
|
1
4
|
import { pruneEngines } from "./pruneEngines.mjs";
|
|
2
|
-
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { basename, join } from "node:path";
|
|
4
5
|
|
|
5
6
|
/** Files whose contents carry the app's identity and have to be rewritten, not copied. */
|
|
6
7
|
function personalise(entry, text, { name, appName }) {
|
|
@@ -38,26 +39,12 @@ function manifest(source, { name, appName, templateName }) {
|
|
|
38
39
|
)}\n`;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
/**
|
|
42
|
-
* Installed, generated or platform-specific: none of it belongs in a generated app.
|
|
43
|
-
*
|
|
44
|
-
* A bundled template never contains these, but `--from` points at a live working copy that usually
|
|
45
|
-
* does - and `node_modules` is full of directory symlinks, which `isDirectory()` reports as files and
|
|
46
|
-
* `readFileSync` then rejects with EISDIR. `--from` could not generate anything at all.
|
|
47
|
-
*/
|
|
48
|
-
const SKIP = new Set(["node_modules", "dist", "android", "ios", ".git", "pnpm-lock.yaml"]);
|
|
49
|
-
|
|
50
42
|
function copyTree(from, to, transform) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
if (entry.isDirectory()) {
|
|
57
|
-
copyTree(source, target, transform);
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
const rewritten = transform(entry.name, source);
|
|
43
|
+
for (const file of listTemplateFiles(from)) {
|
|
44
|
+
const source = join(from, file);
|
|
45
|
+
const target = join(to, file);
|
|
46
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
47
|
+
const rewritten = transform(basename(file), source);
|
|
61
48
|
if (rewritten === null) cpSync(source, target);
|
|
62
49
|
else writeFileSync(target, rewritten);
|
|
63
50
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join, relative, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generated, installed or platform-specific: none of it belongs in a template.
|
|
7
|
+
*
|
|
8
|
+
* Only consulted when git cannot answer - a bundled template carries no repository.
|
|
9
|
+
*/
|
|
10
|
+
const SKIP = new Set(["node_modules", "dist", "android", "ios", ".git", "pnpm-lock.yaml"]);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The files a template consists of, as paths relative to `dir`.
|
|
14
|
+
*
|
|
15
|
+
* Asks git first, and falls back to walking only when git cannot answer. A name blocklist is the
|
|
16
|
+
* wrong instrument for a working copy: it lets through anything nobody thought to name, and a
|
|
17
|
+
* maintainer's stray `build/` and a deploy log both reached a generated app that way. Git already
|
|
18
|
+
* knows what belongs - it is the same list the template fingerprint is computed from, so what ships
|
|
19
|
+
* and what is hashed cannot drift apart.
|
|
20
|
+
*/
|
|
21
|
+
export function listTemplateFiles(dir) {
|
|
22
|
+
const tracked = trackedFiles(dir);
|
|
23
|
+
return tracked ?? walk(dir, dir);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function trackedFiles(dir) {
|
|
27
|
+
if (!existsSync(join(dir, ".git")) && !insideRepository(dir)) return null;
|
|
28
|
+
try {
|
|
29
|
+
const listing = execFileSync("git", ["ls-files", "-z"], {
|
|
30
|
+
cwd: dir,
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
33
|
+
});
|
|
34
|
+
const files = listing
|
|
35
|
+
.split("\0")
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.map((file) => file.split("/").join(sep));
|
|
38
|
+
return files.length ? files.filter((file) => !SKIP.has(file.split(sep)[0])) : null;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function insideRepository(dir) {
|
|
45
|
+
try {
|
|
46
|
+
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: dir, stdio: "ignore" });
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function walk(root, dir) {
|
|
54
|
+
const found = [];
|
|
55
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
56
|
+
if (SKIP.has(entry.name)) continue;
|
|
57
|
+
const full = join(dir, entry.name);
|
|
58
|
+
if (entry.isDirectory()) found.push(...walk(root, full));
|
|
59
|
+
else found.push(relative(root, full));
|
|
60
|
+
}
|
|
61
|
+
return found;
|
|
62
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cavulsqa/create",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.2",
|
|
4
4
|
"description": "Create a Vue + Framework7 + Capacitor + SQLite app from the cavulsqa templates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"capacitor",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
}
|
|
53
53
|
]
|
|
54
54
|
},
|
|
55
|
-
"templatesFingerprint": "sha256-
|
|
55
|
+
"templatesFingerprint": "sha256-w5ItqHFEFJCJwe7V3W0FEhJf7foJkanjYLY85j/Wbd8=",
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "node scripts/bundleTemplates.mjs",
|
|
58
58
|
"check": "vp check",
|
|
@@ -37,8 +37,17 @@ export const DEFAULT_ORDER: StorageCandidate[] = [
|
|
|
37
37
|
* A typo is reported and ignored rather than fatal: it should not brick the build's output, but a
|
|
38
38
|
* setting that was silently dropped looks exactly like one that was applied and did nothing.
|
|
39
39
|
*/
|
|
40
|
+
/**
|
|
41
|
+
* What `VITE_STORAGE_ENGINE` was at build time, or undefined.
|
|
42
|
+
*
|
|
43
|
+
* Exported so the open can report it. Vite inlines this when it bundles, so an empty value means
|
|
44
|
+
* the variable never reached the build - a `.env` written as UTF-16 by PowerShell's `>` parses as
|
|
45
|
+
* garbage and is dropped without a word.
|
|
46
|
+
*/
|
|
47
|
+
export const preferredEngine = import.meta.env.VITE_STORAGE_ENGINE;
|
|
48
|
+
|
|
40
49
|
function preferredFirst(candidates: StorageCandidate[]): StorageCandidate[] {
|
|
41
|
-
const preferred =
|
|
50
|
+
const preferred = preferredEngine;
|
|
42
51
|
if (!preferred) return candidates;
|
|
43
52
|
|
|
44
53
|
if (!isStorageId(preferred)) {
|
|
@@ -117,7 +117,6 @@
|
|
|
117
117
|
"footer": "Change these screens in src/modules/settings/.",
|
|
118
118
|
"storage": "Storage",
|
|
119
119
|
"engine": "Database engine",
|
|
120
|
-
"engineHint": "Every query reaches SQLite compiled to WebAssembly, against a real file in the Origin Private File System, in a worker - no native bridge. The same engine in a browser as on a phone.",
|
|
121
120
|
"webview": "WebView",
|
|
122
121
|
"notDurable": "Storage is not persisted"
|
|
123
122
|
},
|
|
@@ -117,7 +117,6 @@
|
|
|
117
117
|
"footer": "Modifiez ces écrans dans src/modules/settings/.",
|
|
118
118
|
"storage": "Stockage",
|
|
119
119
|
"engine": "Moteur de base de données",
|
|
120
|
-
"engineHint": "Chaque requête atteint SQLite compilé en WebAssembly, sur un vrai fichier dans l'Origin Private File System, dans un worker - sans pont natif. Le même moteur dans un navigateur que sur un téléphone.",
|
|
121
120
|
"webview": "WebView",
|
|
122
121
|
"notDurable": "Le stockage n'est pas persistant"
|
|
123
122
|
},
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</F7ListItem>
|
|
53
53
|
</F7List>
|
|
54
54
|
<F7Block class="mt-0!">
|
|
55
|
-
<p class="m-0 text-[13px] opacity-60">{{
|
|
55
|
+
<p class="m-0 text-[13px] opacity-60">{{ tradeoff }}</p>
|
|
56
56
|
</F7Block>
|
|
57
57
|
|
|
58
58
|
<F7BlockTitle>{{ t("settings.tryIt") }}</F7BlockTitle>
|
|
@@ -3,7 +3,7 @@ import { Migrator } from "kysely/migration";
|
|
|
3
3
|
import { runWrite, type MobileDatabase } from "@cavulsqa/mobile-db";
|
|
4
4
|
import { createChangeBus, createReactiveDb } from "@cavulsqa/reactive-db";
|
|
5
5
|
import { pragmaProfile, pragmasFor } from "@/app/pragmas.config";
|
|
6
|
-
import { storageChain } from "@/app/storage.config";
|
|
6
|
+
import { preferredEngine, storageChain } from "@/app/storage.config";
|
|
7
7
|
import { isStorageId, type StorageAttempt, type StorageCandidate } from "./candidates";
|
|
8
8
|
import { migrations } from "./migrations";
|
|
9
9
|
import { describeOpenFailure } from "./storage";
|
|
@@ -155,6 +155,14 @@ async function tryEveryCandidate(): Promise<MobileDatabase<Database>> {
|
|
|
155
155
|
applied = opened.pragmas;
|
|
156
156
|
chosen = candidate;
|
|
157
157
|
attempts.push({ id: candidate.id, outcome: "opened" });
|
|
158
|
+
// Every launch names its engine. A preference that never arrived - a `.env` the build could
|
|
159
|
+
// not parse, a typo - otherwise looks exactly like one that was applied, and the app reports
|
|
160
|
+
// an engine nobody chose.
|
|
161
|
+
console.info(
|
|
162
|
+
`[storage] opened on ${candidate.id}` +
|
|
163
|
+
(preferredEngine ? ` (VITE_STORAGE_ENGINE=${preferredEngine})` : " (no preference set)"),
|
|
164
|
+
attempts,
|
|
165
|
+
);
|
|
158
166
|
return database;
|
|
159
167
|
} catch (error) {
|
|
160
168
|
attempts.push({ id: candidate.id, outcome: "failed", detail: describeOpenFailure(error) });
|
|
@@ -91,5 +91,13 @@ export default defineConfig({
|
|
|
91
91
|
server: { port: 5173 },
|
|
92
92
|
build: { target: "esnext" },
|
|
93
93
|
lint: { options: { typeAware: false } },
|
|
94
|
-
|
|
94
|
+
/**
|
|
95
|
+
* The unplugin declaration files are committed - vue-tsc has no idea what `ref` or an F7
|
|
96
|
+
* component is without them - but the generator rewrites them on every run, and what it emits is
|
|
97
|
+
* not what oxfmt emits. Formatting them means a build and a commit hook take turns rewriting a
|
|
98
|
+
* file no human wrote and no human can keep formatted.
|
|
99
|
+
*/
|
|
100
|
+
fmt: {
|
|
101
|
+
ignorePatterns: ["**/auto-imports.d.ts", "**/components.d.ts"],
|
|
102
|
+
},
|
|
95
103
|
});
|