@human-synthesis/norns 0.0.11 → 0.0.13
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/package.json +2 -2
- package/src/migrate.js +44 -12
- package/src/server/db.js +19 -1
- package/src/vite.js +20 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@human-synthesis/norns",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "Norns — SvelteKit with Civet, Pug, and the .n / .civet / .c file extensions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Daniel Teodoroiu (https://humansynthesis.ai)",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@danielx/civet": "^0.11.0",
|
|
39
|
-
"@human-synthesis/norns-core": "^0.0.
|
|
39
|
+
"@human-synthesis/norns-core": "^0.0.10"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": ">=18"
|
package/src/migrate.js
CHANGED
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
* tooling (this CLI, wrangler for D1) reads. Keeping them out of `src/lib`
|
|
11
11
|
* also keeps SvelteKit's bundler from ever trying to ship them.
|
|
12
12
|
*
|
|
13
|
-
* v1 supports SQLite
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
13
|
+
* v1 supports SQLite only. Backend selection is runtime-detected:
|
|
14
|
+
* - Under Bun: built-in `bun:sqlite` (no native build, works on Alpine).
|
|
15
|
+
* - Under Node: `better-sqlite3` (must be installed in the consumer app).
|
|
16
|
+
* Postgres/libSQL come later; Cloudflare D1 is intentionally out of scope
|
|
17
|
+
* here — use `wrangler d1 migrations apply <db>` for D1 deploys.
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
20
|
import { existsSync, readdirSync, statSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
@@ -115,11 +117,21 @@ export function resolveDatabaseUrl(cwd) {
|
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
/**
|
|
118
|
-
* Open a
|
|
119
|
-
*
|
|
120
|
+
* Open a SQLite db at `path` and ensure the migration tracking table exists.
|
|
121
|
+
*
|
|
122
|
+
* Backend is runtime-selected: `bun:sqlite` under Bun (no native build,
|
|
123
|
+
* works on Alpine where `better-sqlite3`'s N-API binding fails to load
|
|
124
|
+
* against Bun's V8 compat layer), `better-sqlite3` under Node.
|
|
125
|
+
*
|
|
126
|
+
* The returned object exposes the better-sqlite3 surface used by the
|
|
127
|
+
* migration code (`pragma`, `exec`, `prepare(...).all/get/run`,
|
|
128
|
+
* `transaction`, `close`). Under Bun a minimal `pragma()` shim is grafted
|
|
129
|
+
* on — bun:sqlite has no built-in `pragma` method, but `exec('PRAGMA …')`
|
|
130
|
+
* is equivalent for the writes the migrate code performs.
|
|
120
131
|
*
|
|
121
132
|
* @param {string} cwd directory whose `package.json` is used to resolve
|
|
122
133
|
* better-sqlite3 from the consumer's node_modules
|
|
134
|
+
* (only relevant under Node)
|
|
123
135
|
* @param {string} path SQLite file path
|
|
124
136
|
* @param {{ requireFrom?: string | URL }} [opts] override the require base
|
|
125
137
|
* (used by tests)
|
|
@@ -127,6 +139,32 @@ export function resolveDatabaseUrl(cwd) {
|
|
|
127
139
|
*/
|
|
128
140
|
export function openSqliteDb(cwd, path, opts = {}) {
|
|
129
141
|
mkdirSync(dirname(path), { recursive: true });
|
|
142
|
+
const db = openRawSqlite(cwd, path, opts);
|
|
143
|
+
db.pragma('journal_mode = WAL');
|
|
144
|
+
db.exec(`CREATE TABLE IF NOT EXISTS ${MIGRATION_TABLE} (
|
|
145
|
+
id TEXT PRIMARY KEY,
|
|
146
|
+
applied_at INTEGER NOT NULL
|
|
147
|
+
)`);
|
|
148
|
+
return db;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {string} cwd
|
|
153
|
+
* @param {string} path
|
|
154
|
+
* @param {{ requireFrom?: string | URL }} opts
|
|
155
|
+
* @returns {any}
|
|
156
|
+
*/
|
|
157
|
+
function openRawSqlite(cwd, path, opts) {
|
|
158
|
+
if (typeof Bun !== 'undefined') {
|
|
159
|
+
const r = createRequire(import.meta.url);
|
|
160
|
+
const { Database } = r('bun:sqlite');
|
|
161
|
+
const db = new Database(path);
|
|
162
|
+
// bun:sqlite has no `pragma()` method. Norns calls it only with
|
|
163
|
+
// write-style statements (`'journal_mode = WAL'`, `'foreign_keys = ON'`),
|
|
164
|
+
// for which `exec('PRAGMA …')` is the documented equivalent.
|
|
165
|
+
db.pragma = (stmt) => db.exec('PRAGMA ' + stmt);
|
|
166
|
+
return db;
|
|
167
|
+
}
|
|
130
168
|
const require = createRequire(opts.requireFrom ?? join(cwd, 'package.json'));
|
|
131
169
|
let Database;
|
|
132
170
|
try {
|
|
@@ -136,13 +174,7 @@ export function openSqliteDb(cwd, path, opts = {}) {
|
|
|
136
174
|
'norns migrate: `better-sqlite3` is not installed in this app. Run: bun add better-sqlite3'
|
|
137
175
|
);
|
|
138
176
|
}
|
|
139
|
-
|
|
140
|
-
db.pragma('journal_mode = WAL');
|
|
141
|
-
db.exec(`CREATE TABLE IF NOT EXISTS ${MIGRATION_TABLE} (
|
|
142
|
-
id TEXT PRIMARY KEY,
|
|
143
|
-
applied_at INTEGER NOT NULL
|
|
144
|
-
)`);
|
|
145
|
-
return db;
|
|
177
|
+
return new Database(path);
|
|
146
178
|
}
|
|
147
179
|
|
|
148
180
|
/**
|
package/src/server/db.js
CHANGED
|
@@ -56,13 +56,31 @@ function importDynamic(mod) {
|
|
|
56
56
|
*/
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* Open a Drizzle instance backed by
|
|
59
|
+
* Open a Drizzle instance backed by SQLite.
|
|
60
|
+
*
|
|
61
|
+
* Backend is runtime-selected: `bun:sqlite` + `drizzle-orm/bun-sqlite` under
|
|
62
|
+
* Bun (built-in, no native build, works on Alpine), `better-sqlite3` +
|
|
63
|
+
* `drizzle-orm/better-sqlite3` under Node. The function name keeps the
|
|
64
|
+
* `betterSqlite` alias for backward compatibility — what actually gets
|
|
65
|
+
* loaded depends on the runtime.
|
|
60
66
|
*
|
|
61
67
|
* @param {string} path SQLite file path (e.g. `data/notes.db`)
|
|
62
68
|
* @param {BetterSqliteOptions} [opts]
|
|
63
69
|
* @returns {Promise<any>}
|
|
64
70
|
*/
|
|
65
71
|
export async function betterSqlite(path, opts = {}) {
|
|
72
|
+
if (typeof Bun !== 'undefined') {
|
|
73
|
+
const [{ Database }, { drizzle }] = await Promise.all([
|
|
74
|
+
importDynamic('bun:sqlite'),
|
|
75
|
+
importDynamic('drizzle-orm/bun-sqlite')
|
|
76
|
+
]);
|
|
77
|
+
const sqlite = new Database(path, opts.connection);
|
|
78
|
+
if (opts.pragma) {
|
|
79
|
+
// bun:sqlite has no `pragma()` method — use `exec('PRAGMA …')`.
|
|
80
|
+
for (const p of opts.pragma) sqlite.exec('PRAGMA ' + p);
|
|
81
|
+
}
|
|
82
|
+
return drizzle(sqlite, opts.drizzle);
|
|
83
|
+
}
|
|
66
84
|
const [{ default: Database }, { drizzle }] = await Promise.all([
|
|
67
85
|
importDynamic('better-sqlite3'),
|
|
68
86
|
importDynamic('drizzle-orm/better-sqlite3')
|
package/src/vite.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFile, stat, realpath, readdir, mkdir, writeFile } from 'node:fs/pro
|
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { compile as compileCivet } from '@danielx/civet';
|
|
5
|
+
import { extractPugClasses } from '@human-synthesis/norns-core/preprocess';
|
|
5
6
|
|
|
6
7
|
const DEFAULT_EXTENSIONS = ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'];
|
|
7
8
|
const NORNS_EXTENSIONS = ['.svelte', '.n', '.civet', '.c'];
|
|
@@ -143,53 +144,34 @@ export function nornsCivetPlugin() {
|
|
|
143
144
|
|
|
144
145
|
/* === pugTailwindExtract ==================================================
|
|
145
146
|
*
|
|
146
|
-
* Tailwind v4's content
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
147
|
+
* Tailwind v4's content scanner only picks up utility candidates from
|
|
148
|
+
* space-separated string contexts (`class="…"`, JS strings, etc.). It does
|
|
149
|
+
* NOT understand Pug's chained-class shorthand: `.flex.items-center.p-4`
|
|
150
|
+
* reads as one dotted token, doesn't match any utility, and gets dropped.
|
|
151
|
+
* Same for chains followed by an attribute paren — `.grid.gap-6(class="…")`.
|
|
151
152
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
153
|
+
* Net effect without help: the HTML emitted by `.n` files has the class
|
|
154
|
+
* names, but Tailwind never generates rules for them. Pages render with
|
|
155
|
+
* silently-missing layout (no error, no warning, just visually wrong).
|
|
155
156
|
*
|
|
156
|
-
* This plugin walks every `.n` file under `root`, extracts each
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
157
|
+
* This plugin walks every `.n` file under `root`, extracts each shorthand
|
|
158
|
+
* class via `extractPugClasses` from `@human-synthesis/norns-core`, and
|
|
159
|
+
* writes the deduplicated set into a sidecar HTML file. Consumers
|
|
160
|
+
* reference the file from their CSS via
|
|
161
|
+
* `@source "./.tailwind-pug-classes.html";`
|
|
162
|
+
* so Tailwind picks it up like any other content source.
|
|
161
163
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
164
|
+
* Extraction skips `<script>` and `<style>` blocks, mixin calls (`+if`),
|
|
165
|
+
* text-emit lines (`|`), raw HTML lines (`<`), and Pug comments (`//`).
|
|
166
|
+
* It also pulls tokens out of any `class="…"` / `class!="…"` attribute on
|
|
167
|
+
* the same line — redundant with Tailwind's own scan in most cases, free
|
|
168
|
+
* defence in depth.
|
|
167
169
|
*
|
|
168
170
|
* Runs with `enforce: 'pre'` so the scan sees the raw Pug source, not the
|
|
169
171
|
* Svelte output that the rest of the chain emits.
|
|
170
172
|
* ========================================================================
|
|
171
173
|
*/
|
|
172
174
|
|
|
173
|
-
/**
|
|
174
|
-
* Match every `.candidate` segment. Class names may contain Tailwind's
|
|
175
|
-
* full alphabet — letters, digits, `-`, `_`, `:`, `/`, and arbitrary-value
|
|
176
|
-
* brackets `[...]`. Pug shorthand never contains a `.` inside a class
|
|
177
|
-
* (the dot is the delimiter), so `text-[1.5rem]`-style values never appear
|
|
178
|
-
* in shorthand — those always live inside `class="…"`, which Tailwind
|
|
179
|
-
* extracts directly.
|
|
180
|
-
*/
|
|
181
|
-
const SEGMENT_RE = /\.([A-Za-z][\w\-:/]*(?:\[[^\]]*\])?)/g;
|
|
182
|
-
|
|
183
|
-
function extractPugClasses(source) {
|
|
184
|
-
const out = new Set();
|
|
185
|
-
let m;
|
|
186
|
-
SEGMENT_RE.lastIndex = 0;
|
|
187
|
-
while ((m = SEGMENT_RE.exec(source))) {
|
|
188
|
-
if (m[1]) out.add(m[1]);
|
|
189
|
-
}
|
|
190
|
-
return out;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
175
|
async function walkNFiles(dir, ext, out = []) {
|
|
194
176
|
let entries;
|
|
195
177
|
try {
|