@human-synthesis/norns 0.0.5 → 0.0.7
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/LICENSE +1 -1
- package/README.md +134 -29
- package/bin/norns.js +306 -0
- package/package.json +14 -7
- package/src/auto-import.js +638 -0
- package/src/config.js +54 -7
- package/src/index.js +2 -2
- package/src/migrate.js +212 -0
- package/src/server/boot.js +74 -0
- package/src/server/container.js +175 -0
- package/src/server/db.js +131 -0
- package/src/server/handle/context.js +24 -0
- package/src/server/handle/error.js +18 -0
- package/src/server/index.js +9 -0
- package/src/server/page.js +107 -0
- package/src/server/route.js +114 -0
- package/src/server/scope.js +50 -0
- package/src/server/validate.js +60 -0
- package/src/vite.js +127 -13
- package/src/uno.js +0 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Norns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**AI-driven software architecture and development framework, based on Svelte.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
SvelteKit with **Pug + Civet** and the `.n` / `.c` file extensions — preconfigured. The `.c` extension is recognised as an alias for `.civet`; both compile through Civet. CoffeeScript is no longer supported.
|
|
6
|
+
|
|
7
|
+
Includes a small runtime layer: feature-folder modularity, a DI container, route/page wrappers with valibot validation, and a migrations CLI.
|
|
8
|
+
|
|
9
|
+
## Stack
|
|
10
|
+
|
|
11
|
+
- [Svelte 5](https://svelte.dev) — components and runes
|
|
12
|
+
- [SvelteKit 2](https://kit.svelte.dev) — file-system routing, SSR, endpoints
|
|
13
|
+
- [Pug](https://pugjs.org) — templates
|
|
14
|
+
- [Civet](https://civet.dev) — script (TypeScript-flavored, indented)
|
|
15
|
+
- [Tailwind CSS v4](https://tailwindcss.com) — recommended styling (consumer-installed)
|
|
16
|
+
- [Vite](https://vitejs.dev) — bundler
|
|
17
|
+
- [bun](https://bun.sh) — runtime / package manager
|
|
6
18
|
|
|
7
19
|
## Install
|
|
8
20
|
|
|
9
21
|
```sh
|
|
10
|
-
|
|
22
|
+
bun add -D @human-synthesis/norns @sveltejs/kit svelte
|
|
11
23
|
```
|
|
12
24
|
|
|
13
|
-
|
|
25
|
+
Or use the [`norns-app`](https://github.com/human-synthesis/norns-app) starter, which has everything wired up.
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
14
28
|
|
|
15
29
|
`svelte.config.js`:
|
|
16
30
|
|
|
@@ -18,8 +32,7 @@ pnpm add -D @human-synthesis/norns @sveltejs/kit svelte unocss vite
|
|
|
18
32
|
import { nornsConfig } from '@human-synthesis/norns/config';
|
|
19
33
|
|
|
20
34
|
export default nornsConfig({
|
|
21
|
-
// your overrides here
|
|
22
|
-
// kit: { adapter: adapterNode() }
|
|
35
|
+
// your overrides here
|
|
23
36
|
});
|
|
24
37
|
```
|
|
25
38
|
|
|
@@ -28,42 +41,134 @@ export default nornsConfig({
|
|
|
28
41
|
```js
|
|
29
42
|
import { defineConfig } from 'vite';
|
|
30
43
|
import { sveltekit } from '@sveltejs/kit/vite';
|
|
31
|
-
import {
|
|
44
|
+
import { nornsCivetPlugin } from '@human-synthesis/norns/vite';
|
|
32
45
|
|
|
33
46
|
export default defineConfig({
|
|
34
|
-
plugins: [
|
|
47
|
+
plugins: [nornsCivetPlugin(), sveltekit()]
|
|
35
48
|
});
|
|
36
49
|
```
|
|
37
50
|
|
|
38
|
-
|
|
51
|
+
`package.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"scripts": {
|
|
56
|
+
"dev": "norns dev",
|
|
57
|
+
"build": "norns build",
|
|
58
|
+
"preview": "norns preview",
|
|
59
|
+
"migrate": "norns migrate"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Auto-imports
|
|
39
65
|
|
|
40
|
-
|
|
41
|
-
- **`.coffee` Kit modules** — write `+page.coffee`, `+page.server.coffee`, `+layout.coffee`, `+server.coffee`, `hooks.server.coffee` instead of `.js`/`.ts`
|
|
42
|
-
- **UnoCSS** preset stack (Uno, Attributify, Icons, Typography) wired into Vite
|
|
66
|
+
`nornsAutoImport()` returns an object that's both a Svelte preprocessor (for `.n` / `.svelte` files) and a Vite plugin (for standalone `.c` / `.civet` modules). Wire it in both places:
|
|
43
67
|
|
|
44
|
-
|
|
68
|
+
```js
|
|
69
|
+
// svelte.config.js
|
|
70
|
+
import { nornsConfig } from '@human-synthesis/norns/config';
|
|
71
|
+
import { nornsPreprocess } from '@human-synthesis/norns/preprocess';
|
|
72
|
+
import { nornsAutoImport } from '@human-synthesis/norns/auto-import';
|
|
45
73
|
|
|
46
|
-
|
|
74
|
+
export default nornsConfig({
|
|
75
|
+
preprocess: [...nornsPreprocess(), nornsAutoImport()]
|
|
76
|
+
});
|
|
77
|
+
```
|
|
47
78
|
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
</template>
|
|
79
|
+
```js
|
|
80
|
+
// vite.config.js
|
|
81
|
+
import { nornsCivetPlugin } from '@human-synthesis/norns/vite';
|
|
82
|
+
import { nornsAutoImport } from '@human-synthesis/norns/auto-import';
|
|
53
83
|
|
|
54
|
-
|
|
55
|
-
export let data
|
|
56
|
-
count = 0
|
|
57
|
-
</script>
|
|
84
|
+
export default { plugins: [nornsCivetPlugin(), nornsAutoImport(), sveltekit()] };
|
|
58
85
|
```
|
|
59
86
|
|
|
60
|
-
|
|
87
|
+
With both in place:
|
|
88
|
+
|
|
89
|
+
- **Svelte helpers** — `onMount`, `tick`, `getContext`, …, plus `svelte/store` (`writable`, `readable`, `derived`, `get`).
|
|
90
|
+
- **SvelteKit helpers** — `error`, `redirect`, `fail`, `json`, `text`, … from `@sveltejs/kit`. Plus `page`, `navigating`, `updated` from `$app/state` — gated to non-server paths so it doesn't collide with the Norns server `page` (different shape, same name).
|
|
91
|
+
- **Norns server helpers** — `boot`, `page`, `route`, `Container`, `validate`, `betterSqlite`, … from `@human-synthesis/norns/server`. Gated to server paths (`*.server.{c,civet}`, `**/server/**`, `+server.{c,civet}`).
|
|
92
|
+
- **Project components** — capitalised references like `<Card>` or `<Modal>` resolve to files under `src/lib/components/**` by default. Components inside `$lib` emit `$lib/...` import paths; components outside (e.g. route-colocated under `src/routes/**`, when you add it to `componentDirs`) emit a path relative to the importer. Files without a `<script>` block get one prepended automatically.
|
|
93
|
+
- **Runes** (`$state`, `$derived`, `$effect`, `$props`) are Svelte compiler globals — no import needed; the plugin doesn't touch them.
|
|
94
|
+
|
|
95
|
+
Configurable on `nornsAutoImport({ … })`: `helpers` (each entry can carry an optional `match: RegExp` to gate by filename), `componentDirs`, `componentExtensions`, `libRoot`, `libAlias`. Pass `helpers: false` or `componentDirs: false` to disable either layer.
|
|
96
|
+
|
|
97
|
+
## Runtime — feature folders + DI
|
|
98
|
+
|
|
99
|
+
Wire your hooks once:
|
|
61
100
|
|
|
62
101
|
```coffee
|
|
63
|
-
|
|
64
|
-
|
|
102
|
+
# src/hooks.server.c
|
|
103
|
+
import { boot } from '@human-synthesis/norns/server'
|
|
104
|
+
|
|
105
|
+
features := import.meta.glob './lib/*/server/module.c', { eager: true }
|
|
106
|
+
app := await boot { features }
|
|
107
|
+
|
|
108
|
+
{ handle, handleError } := app
|
|
109
|
+
export { handle, handleError }
|
|
65
110
|
```
|
|
66
111
|
|
|
67
|
-
|
|
112
|
+
Each feature is a folder under `src/lib/<feature>/`:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
src/lib/notes/
|
|
116
|
+
server/
|
|
117
|
+
module.c # registers DI bindings + migrations
|
|
118
|
+
repo.c # SQL / data access
|
|
119
|
+
service.c # business logic
|
|
120
|
+
public.c # the ONLY file other features may import
|
|
121
|
+
shared/
|
|
122
|
+
schema.c # valibot validation schemas
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Routes use thin wrappers from `@human-synthesis/norns/server`:
|
|
126
|
+
|
|
127
|
+
```coffee
|
|
128
|
+
# src/routes/notes/+page.server.c
|
|
129
|
+
import { page } from '@human-synthesis/norns/server'
|
|
130
|
+
import { notes } from '$lib/notes/server/public'
|
|
131
|
+
import { createNoteSchema } from '$lib/notes/shared/schema'
|
|
132
|
+
|
|
133
|
+
export load := page.load
|
|
134
|
+
handler: ({ container }) =>
|
|
135
|
+
notes: notes(container).list()
|
|
136
|
+
|
|
137
|
+
export actions := page.actions
|
|
138
|
+
create:
|
|
139
|
+
input: createNoteSchema
|
|
140
|
+
run: ({ input, container }) =>
|
|
141
|
+
id := notes(container).create input
|
|
142
|
+
throw redirect 303, `/notes/${id}`
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The wrappers handle: input parsing, [valibot](https://valibot.dev) validation, container resolution, and consistent error mapping.
|
|
146
|
+
|
|
147
|
+
## Migrations
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
bun run migrate create notes/add_pinned # scaffold migrations/notes/<ts>_add_pinned.sql
|
|
151
|
+
bun run migrate up # apply pending migrations
|
|
152
|
+
bun run migrate status # list applied + pending
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Migration files live at `<project>/migrations/<feature>/*.sql`. The CLI tracks applied migrations in a `norns_migrations` table.
|
|
156
|
+
|
|
157
|
+
v1 supports SQLite via `better-sqlite3`. For Cloudflare D1 use `wrangler d1 migrations apply`. Postgres / libSQL via the CLI are planned.
|
|
158
|
+
|
|
159
|
+
## Drivers
|
|
160
|
+
|
|
161
|
+
The `db` helpers wire Drizzle across multiple targets:
|
|
162
|
+
|
|
163
|
+
```coffee
|
|
164
|
+
# module.c — Node + better-sqlite3 in dev
|
|
165
|
+
import { betterSqlite } from '@human-synthesis/norns/server'
|
|
166
|
+
db := await betterSqlite 'data/app.db', { pragma: ['journal_mode = WAL'] }
|
|
167
|
+
app.single 'db', => db
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
D1, libSQL, and Postgres factories ship in the same module; the driver packages are user-installed (peer-style).
|
|
171
|
+
|
|
172
|
+
## License
|
|
68
173
|
|
|
69
|
-
MIT © Human Synthesis. Built on top of [SvelteKit](https://github.com/sveltejs/kit) and [Svelte](https://github.com/sveltejs/svelte) © Svelte Contributors, MIT licensed.
|
|
174
|
+
MIT © Daniel Teodoroiu / [Human Synthesis](https://humansynthesis.ai). Built on top of [SvelteKit](https://github.com/sveltejs/kit) and [Svelte](https://github.com/sveltejs/svelte) © Svelte Contributors, MIT licensed.
|
package/bin/norns.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { watch, realpathSync, readFileSync, lstatSync, readdirSync, rmSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import {
|
|
7
|
+
listMigrations,
|
|
8
|
+
resolveDatabaseUrl,
|
|
9
|
+
openSqliteDb,
|
|
10
|
+
getApplied,
|
|
11
|
+
applyMigrations,
|
|
12
|
+
createMigration
|
|
13
|
+
} from '../src/migrate.js';
|
|
14
|
+
|
|
15
|
+
const FRAMEWORK_PKGS = ['@human-synthesis/norns-core', '@human-synthesis/norns'];
|
|
16
|
+
|
|
17
|
+
function resolveWorkspaceFrameworkSrcs(root) {
|
|
18
|
+
const require = createRequire(join(root, 'package.json'));
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const pkg of FRAMEWORK_PKGS) {
|
|
21
|
+
try {
|
|
22
|
+
const real = realpathSync(require.resolve(`${pkg}/package.json`));
|
|
23
|
+
const pkgDir = dirname(real);
|
|
24
|
+
if (!pkgDir.includes(`${join('/', 'node_modules', '/')}`)) {
|
|
25
|
+
out.push(join(pkgDir, 'src'));
|
|
26
|
+
}
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* In workspace mode (a parent node_modules has framework packages as symlinks),
|
|
34
|
+
* a `bun add <pkg>` from the consumer dir often drops the *published* version
|
|
35
|
+
* of @human-synthesis/* into the local node_modules, which then shadows the
|
|
36
|
+
* workspace symlinks. The shadow is the npm-published code, not the local
|
|
37
|
+
* source — silently breaks dev. This detects the shadow and removes it.
|
|
38
|
+
*
|
|
39
|
+
* Only acts when both conditions hold:
|
|
40
|
+
* 1. some ancestor node_modules has the framework package as a symlink
|
|
41
|
+
* (proves we're in workspace mode)
|
|
42
|
+
* 2. the cwd-local node_modules has the same package as a real directory
|
|
43
|
+
* (the shadow that's overriding the symlink)
|
|
44
|
+
*
|
|
45
|
+
* No-op for normal installs (no symlinked ancestor → nothing to shadow).
|
|
46
|
+
*
|
|
47
|
+
* @param {string} cwd
|
|
48
|
+
* @returns {string[]} package names that were cleaned
|
|
49
|
+
*/
|
|
50
|
+
function cleanShadowedFrameworkPkgs(cwd) {
|
|
51
|
+
// Walk up from cwd looking for a parent with a framework package as symlink.
|
|
52
|
+
let workspaceMode = false;
|
|
53
|
+
let dir = dirname(cwd);
|
|
54
|
+
while (dir !== dirname(dir)) {
|
|
55
|
+
for (const pkg of FRAMEWORK_PKGS) {
|
|
56
|
+
try {
|
|
57
|
+
const stat = lstatSync(join(dir, 'node_modules', ...pkg.split('/')));
|
|
58
|
+
if (stat.isSymbolicLink()) {
|
|
59
|
+
workspaceMode = true;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
}
|
|
64
|
+
if (workspaceMode) break;
|
|
65
|
+
dir = dirname(dir);
|
|
66
|
+
}
|
|
67
|
+
if (!workspaceMode) return [];
|
|
68
|
+
|
|
69
|
+
const removed = [];
|
|
70
|
+
for (const pkg of FRAMEWORK_PKGS) {
|
|
71
|
+
const shadowPath = join(cwd, 'node_modules', ...pkg.split('/'));
|
|
72
|
+
try {
|
|
73
|
+
const stat = lstatSync(shadowPath);
|
|
74
|
+
// lstat doesn't follow symlinks — a symlinked dir reports
|
|
75
|
+
// isDirectory() === false, so this only matches real dirs.
|
|
76
|
+
if (stat.isDirectory()) {
|
|
77
|
+
rmSync(shadowPath, { recursive: true, force: true });
|
|
78
|
+
removed.push(pkg);
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Tidy up an emptied @human-synthesis/ scope dir if it has no other content.
|
|
84
|
+
const scopeDir = join(cwd, 'node_modules', '@human-synthesis');
|
|
85
|
+
try {
|
|
86
|
+
if (readdirSync(scopeDir).length === 0) rmSync(scopeDir, { recursive: true, force: true });
|
|
87
|
+
} catch {}
|
|
88
|
+
|
|
89
|
+
return removed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function findViteBin(root) {
|
|
93
|
+
const require = createRequire(join(root, 'package.json'));
|
|
94
|
+
const pkgPath = require.resolve('vite/package.json');
|
|
95
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
96
|
+
const binEntry = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.vite;
|
|
97
|
+
if (!binEntry) throw new Error('vite package has no bin entry');
|
|
98
|
+
return join(dirname(pkgPath), binEntry);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function devCommand(passthrough) {
|
|
102
|
+
const cwd = process.cwd();
|
|
103
|
+
const cleaned = cleanShadowedFrameworkPkgs(cwd);
|
|
104
|
+
if (cleaned.length > 0) {
|
|
105
|
+
console.log(
|
|
106
|
+
`[norns] removed shadowed framework packages from local node_modules: ${cleaned.join(', ')} ` +
|
|
107
|
+
`— the workspace symlinks at the parent will be used instead.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const viteBin = findViteBin(cwd);
|
|
111
|
+
const watchSrcs = resolveWorkspaceFrameworkSrcs(cwd);
|
|
112
|
+
|
|
113
|
+
let child = null;
|
|
114
|
+
let restarting = false;
|
|
115
|
+
let pendingRestart = false;
|
|
116
|
+
|
|
117
|
+
function spawnVite() {
|
|
118
|
+
child = spawn(process.execPath, [viteBin, 'dev', ...passthrough], {
|
|
119
|
+
cwd,
|
|
120
|
+
stdio: 'inherit',
|
|
121
|
+
env: process.env
|
|
122
|
+
});
|
|
123
|
+
child.on('exit', (code, signal) => {
|
|
124
|
+
child = null;
|
|
125
|
+
if (restarting) {
|
|
126
|
+
restarting = false;
|
|
127
|
+
if (pendingRestart) {
|
|
128
|
+
pendingRestart = false;
|
|
129
|
+
}
|
|
130
|
+
spawnVite();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function restart(reason) {
|
|
138
|
+
if (restarting) {
|
|
139
|
+
pendingRestart = true;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
restarting = true;
|
|
143
|
+
console.log(`\n[norns] ${reason} — respawning vite dev for fresh module cache.\n`);
|
|
144
|
+
if (child && child.exitCode === null) child.kill('SIGTERM');
|
|
145
|
+
else spawnVite();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let debounce = null;
|
|
149
|
+
function onChange(file) {
|
|
150
|
+
clearTimeout(debounce);
|
|
151
|
+
debounce = setTimeout(() => {
|
|
152
|
+
restart(`framework source changed (${file})`);
|
|
153
|
+
}, 100);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const src of watchSrcs) {
|
|
157
|
+
try {
|
|
158
|
+
watch(src, { recursive: true }, (_event, filename) => {
|
|
159
|
+
if (!filename) return;
|
|
160
|
+
onChange(join(src, filename));
|
|
161
|
+
});
|
|
162
|
+
console.log(`[norns] watching framework src: ${src}`);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.warn(`[norns] could not watch ${src}: ${err.message}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const sig of ['SIGINT', 'SIGTERM']) {
|
|
169
|
+
process.on(sig, () => {
|
|
170
|
+
if (child && child.exitCode === null) child.kill(sig);
|
|
171
|
+
else process.exit(0);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
spawnVite();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function passthroughCommand(name, passthrough) {
|
|
179
|
+
const cwd = process.cwd();
|
|
180
|
+
const cleaned = cleanShadowedFrameworkPkgs(cwd);
|
|
181
|
+
if (cleaned.length > 0) {
|
|
182
|
+
console.log(
|
|
183
|
+
`[norns] removed shadowed framework packages from local node_modules: ${cleaned.join(', ')}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const viteBin = findViteBin(cwd);
|
|
187
|
+
const child = spawn(process.execPath, [viteBin, name, ...passthrough], {
|
|
188
|
+
cwd,
|
|
189
|
+
stdio: 'inherit',
|
|
190
|
+
env: process.env
|
|
191
|
+
});
|
|
192
|
+
child.on('exit', (code, signal) => process.exit(code ?? (signal ? 1 : 0)));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function migrateCommand(rest) {
|
|
196
|
+
const sub = rest[0] || 'status';
|
|
197
|
+
const cwd = process.cwd();
|
|
198
|
+
const cleaned = cleanShadowedFrameworkPkgs(cwd);
|
|
199
|
+
if (cleaned.length > 0) {
|
|
200
|
+
console.log(
|
|
201
|
+
`[norns] removed shadowed framework packages from local node_modules: ${cleaned.join(', ')}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
switch (sub) {
|
|
206
|
+
case 'status':
|
|
207
|
+
return runMigrateStatus(cwd);
|
|
208
|
+
case 'up':
|
|
209
|
+
return runMigrateUp(cwd);
|
|
210
|
+
case 'create': {
|
|
211
|
+
const file = createMigration(cwd, rest[1]);
|
|
212
|
+
console.log(`Created ${file}`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
default:
|
|
216
|
+
console.error(`norns migrate: unknown subcommand "${sub}"`);
|
|
217
|
+
console.error('Usage: norns migrate <status|up|create <feature>/<name>>');
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.error(err.message);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function runMigrateStatus(cwd) {
|
|
227
|
+
const all = listMigrations(cwd);
|
|
228
|
+
if (all.length === 0) {
|
|
229
|
+
console.log('No migrations found.');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const db = openTargetDb(cwd);
|
|
233
|
+
const applied = getApplied(db);
|
|
234
|
+
console.log(`Found ${all.length} migration(s):`);
|
|
235
|
+
for (const m of all) {
|
|
236
|
+
const tag = applied.has(m.id) ? '[applied]' : '[pending]';
|
|
237
|
+
console.log(` ${tag} ${m.id}`);
|
|
238
|
+
}
|
|
239
|
+
const pending = all.filter((m) => !applied.has(m.id)).length;
|
|
240
|
+
console.log(`\n${pending} pending, ${all.length - pending} applied.`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function runMigrateUp(cwd) {
|
|
244
|
+
const all = listMigrations(cwd);
|
|
245
|
+
if (all.length === 0) {
|
|
246
|
+
console.log('No migrations found.');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const db = openTargetDb(cwd);
|
|
250
|
+
const applied = getApplied(db);
|
|
251
|
+
const pending = all.filter((m) => !applied.has(m.id));
|
|
252
|
+
if (pending.length === 0) {
|
|
253
|
+
console.log('Nothing to apply — all migrations are up to date.');
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
console.log(`Applying ${pending.length} migration(s)...`);
|
|
257
|
+
for (const m of pending) {
|
|
258
|
+
try {
|
|
259
|
+
applyMigrations(db, [m]);
|
|
260
|
+
console.log(` [ok] ${m.id}`);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
console.error(` [fail] ${m.id}: ${err.message}`);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
console.log('Done.');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function openTargetDb(cwd) {
|
|
270
|
+
const target = resolveDatabaseUrl(cwd);
|
|
271
|
+
return openSqliteDb(cwd, target.path);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const [, , cmd = 'dev', ...rest] = process.argv;
|
|
275
|
+
|
|
276
|
+
switch (cmd) {
|
|
277
|
+
case 'dev':
|
|
278
|
+
devCommand(rest);
|
|
279
|
+
break;
|
|
280
|
+
case 'build':
|
|
281
|
+
case 'preview':
|
|
282
|
+
passthroughCommand(cmd, rest);
|
|
283
|
+
break;
|
|
284
|
+
case 'migrate':
|
|
285
|
+
migrateCommand(rest);
|
|
286
|
+
break;
|
|
287
|
+
case '-h':
|
|
288
|
+
case '--help':
|
|
289
|
+
console.log(`norns <command>
|
|
290
|
+
|
|
291
|
+
Commands:
|
|
292
|
+
dev start vite dev with framework-source watching (default)
|
|
293
|
+
build run vite build
|
|
294
|
+
preview run vite preview
|
|
295
|
+
migrate status list applied + pending migrations
|
|
296
|
+
migrate up apply pending migrations
|
|
297
|
+
migrate create <feature>/<name> scaffold a new SQL migration
|
|
298
|
+
|
|
299
|
+
Migration db is read from \$DATABASE_URL (default: file:./data/app.db).
|
|
300
|
+
Only file: (better-sqlite3) is supported in v1; for D1 use \`wrangler d1 migrations apply\`.
|
|
301
|
+
`);
|
|
302
|
+
break;
|
|
303
|
+
default:
|
|
304
|
+
console.error(`norns: unknown command "${cmd}"`);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@human-synthesis/norns",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Norns — SvelteKit with
|
|
3
|
+
"version": "0.0.7",
|
|
4
|
+
"description": "Norns — SvelteKit with Civet, Pug, and the .n / .civet / .c file extensions",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"author": "
|
|
6
|
+
"author": "Daniel Teodoroiu (https://humansynthesis.ai)",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -11,25 +11,32 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"src",
|
|
14
|
+
"bin",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|
|
17
|
+
"bin": {
|
|
18
|
+
"norns": "./bin/norns.js"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "bun test"
|
|
22
|
+
},
|
|
16
23
|
"exports": {
|
|
17
24
|
".": "./src/index.js",
|
|
25
|
+
"./auto-import": "./src/auto-import.js",
|
|
18
26
|
"./config": "./src/config.js",
|
|
19
27
|
"./vite": "./src/vite.js",
|
|
20
28
|
"./preprocess": "./src/preprocess.js",
|
|
21
|
-
"./
|
|
29
|
+
"./server": "./src/server/index.js",
|
|
22
30
|
"./package.json": "./package.json"
|
|
23
31
|
},
|
|
24
32
|
"peerDependencies": {
|
|
25
33
|
"@sveltejs/kit": "^2.0.0",
|
|
26
34
|
"svelte": "^5.0.0",
|
|
27
|
-
"unocss": "^0.65.0",
|
|
28
35
|
"vite": "^5.0.0 || ^6.0.0"
|
|
29
36
|
},
|
|
30
37
|
"dependencies": {
|
|
31
|
-
"@
|
|
32
|
-
"
|
|
38
|
+
"@danielx/civet": "^0.11.0",
|
|
39
|
+
"@human-synthesis/norns-core": "^0.0.7"
|
|
33
40
|
},
|
|
34
41
|
"engines": {
|
|
35
42
|
"node": ">=18"
|