@human-synthesis/norns 0.0.5 → 0.0.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Human Synthesis
3
+ Copyright (c) 2026 Daniel Teodoroiu / Human Synthesis
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,16 +1,28 @@
1
- # @human-synthesis/norns
1
+ # Norns
2
2
 
3
- SvelteKit with CoffeeScript, Pug, and UnoCSS preconfigured.
3
+ **AI-driven software architecture and development framework, based on Svelte.**
4
4
 
5
- Builds on top of [`@human-synthesis/norns-core`](https://github.com/human-synthesis/norns-core) and adds first-class support for `.coffee` SvelteKit special files (`+page.coffee`, `+page.server.coffee`, `hooks.server.coffee`, `+server.coffee`, etc.).
5
+ SvelteKit with Pug, CoffeeScript, and `.n` / `.c` files preconfigured.
6
+
7
+ ## Stack
8
+
9
+ - [Svelte 5](https://svelte.dev) — components and runes
10
+ - [SvelteKit 2](https://kit.svelte.dev) — file-system routing, SSR, endpoints
11
+ - [Pug](https://pugjs.org) — templates
12
+ - [CoffeeScript 2](https://coffeescript.org) — script
13
+ - [Tailwind CSS v4](https://tailwindcss.com) — recommended styling
14
+ - [Vite](https://vitejs.dev) — bundler
15
+ - [bun](https://bun.sh) — runtime / package manager
6
16
 
7
17
  ## Install
8
18
 
9
19
  ```sh
10
- pnpm add -D @human-synthesis/norns @sveltejs/kit svelte unocss vite
20
+ bun add -D @human-synthesis/norns @sveltejs/kit svelte
11
21
  ```
12
22
 
13
- ## Usage
23
+ Or use the [`norns-app`](https://github.com/human-synthesis/norns-app) starter, which has everything wired up.
24
+
25
+ ## Setup
14
26
 
15
27
  `svelte.config.js`:
16
28
 
@@ -18,8 +30,7 @@ pnpm add -D @human-synthesis/norns @sveltejs/kit svelte unocss vite
18
30
  import { nornsConfig } from '@human-synthesis/norns/config';
19
31
 
20
32
  export default nornsConfig({
21
- // your overrides here, e.g.:
22
- // kit: { adapter: adapterNode() }
33
+ // your overrides here
23
34
  });
24
35
  ```
25
36
 
@@ -28,42 +39,25 @@ export default nornsConfig({
28
39
  ```js
29
40
  import { defineConfig } from 'vite';
30
41
  import { sveltekit } from '@sveltejs/kit/vite';
31
- import { nornsCoffeePlugin, nornsUno } from '@human-synthesis/norns';
42
+ import { nornsCoffeePlugin } from '@human-synthesis/norns/vite';
32
43
 
33
44
  export default defineConfig({
34
- plugins: [nornsCoffeePlugin(), nornsUno(), sveltekit()]
45
+ plugins: [nornsCoffeePlugin(), sveltekit()]
35
46
  });
36
47
  ```
37
48
 
38
- ## What this gives you
39
-
40
- - **`.svelte` files** with `<script lang="coffee">`, `<template lang="pug">`, and UnoCSS class attributes
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
43
-
44
- ## Example route
45
-
46
- `src/routes/+page.svelte`:
47
-
48
- ```svelte
49
- <template lang="pug">
50
- h1.text-3xl.font-bold Hello {data.name}
51
- button(on:click="{() => count++}") count is {count}
52
- </template>
53
-
54
- <script lang="coffee">
55
- export let data
56
- count = 0
57
- </script>
58
- ```
59
-
60
- `src/routes/+page.server.coffee`:
49
+ `package.json`:
61
50
 
62
- ```coffee
63
- export load = ->
64
- name: 'Norns'
51
+ ```json
52
+ {
53
+ "scripts": {
54
+ "dev": "norns dev",
55
+ "build": "norns build",
56
+ "preview": "norns preview"
57
+ }
58
+ }
65
59
  ```
66
60
 
67
- ## License & attribution
61
+ ## License
68
62
 
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.
63
+ 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,137 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { watch, realpathSync, readFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { createRequire } from 'node:module';
6
+
7
+ const FRAMEWORK_PKGS = ['@human-synthesis/norns-core', '@human-synthesis/norns'];
8
+
9
+ function resolveWorkspaceFrameworkSrcs(root) {
10
+ const require = createRequire(join(root, 'package.json'));
11
+ const out = [];
12
+ for (const pkg of FRAMEWORK_PKGS) {
13
+ try {
14
+ const real = realpathSync(require.resolve(`${pkg}/package.json`));
15
+ const pkgDir = dirname(real);
16
+ if (!pkgDir.includes(`${join('/', 'node_modules', '/')}`)) {
17
+ out.push(join(pkgDir, 'src'));
18
+ }
19
+ } catch {}
20
+ }
21
+ return out;
22
+ }
23
+
24
+ function findViteBin(root) {
25
+ const require = createRequire(join(root, 'package.json'));
26
+ const pkgPath = require.resolve('vite/package.json');
27
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
28
+ const binEntry = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.vite;
29
+ if (!binEntry) throw new Error('vite package has no bin entry');
30
+ return join(dirname(pkgPath), binEntry);
31
+ }
32
+
33
+ function devCommand(passthrough) {
34
+ const cwd = process.cwd();
35
+ const viteBin = findViteBin(cwd);
36
+ const watchSrcs = resolveWorkspaceFrameworkSrcs(cwd);
37
+
38
+ let child = null;
39
+ let restarting = false;
40
+ let pendingRestart = false;
41
+
42
+ function spawnVite() {
43
+ child = spawn(process.execPath, [viteBin, 'dev', ...passthrough], {
44
+ cwd,
45
+ stdio: 'inherit',
46
+ env: process.env
47
+ });
48
+ child.on('exit', (code, signal) => {
49
+ child = null;
50
+ if (restarting) {
51
+ restarting = false;
52
+ if (pendingRestart) {
53
+ pendingRestart = false;
54
+ }
55
+ spawnVite();
56
+ return;
57
+ }
58
+ process.exit(code ?? (signal ? 1 : 0));
59
+ });
60
+ }
61
+
62
+ function restart(reason) {
63
+ if (restarting) {
64
+ pendingRestart = true;
65
+ return;
66
+ }
67
+ restarting = true;
68
+ console.log(`\n[norns] ${reason} — respawning vite dev for fresh module cache.\n`);
69
+ if (child && child.exitCode === null) child.kill('SIGTERM');
70
+ else spawnVite();
71
+ }
72
+
73
+ let debounce = null;
74
+ function onChange(file) {
75
+ clearTimeout(debounce);
76
+ debounce = setTimeout(() => {
77
+ restart(`framework source changed (${file})`);
78
+ }, 100);
79
+ }
80
+
81
+ for (const src of watchSrcs) {
82
+ try {
83
+ watch(src, { recursive: true }, (_event, filename) => {
84
+ if (!filename) return;
85
+ onChange(join(src, filename));
86
+ });
87
+ console.log(`[norns] watching framework src: ${src}`);
88
+ } catch (err) {
89
+ console.warn(`[norns] could not watch ${src}: ${err.message}`);
90
+ }
91
+ }
92
+
93
+ for (const sig of ['SIGINT', 'SIGTERM']) {
94
+ process.on(sig, () => {
95
+ if (child && child.exitCode === null) child.kill(sig);
96
+ else process.exit(0);
97
+ });
98
+ }
99
+
100
+ spawnVite();
101
+ }
102
+
103
+ function passthroughCommand(name, passthrough) {
104
+ const cwd = process.cwd();
105
+ const viteBin = findViteBin(cwd);
106
+ const child = spawn(process.execPath, [viteBin, name, ...passthrough], {
107
+ cwd,
108
+ stdio: 'inherit',
109
+ env: process.env
110
+ });
111
+ child.on('exit', (code, signal) => process.exit(code ?? (signal ? 1 : 0)));
112
+ }
113
+
114
+ const [, , cmd = 'dev', ...rest] = process.argv;
115
+
116
+ switch (cmd) {
117
+ case 'dev':
118
+ devCommand(rest);
119
+ break;
120
+ case 'build':
121
+ case 'preview':
122
+ passthroughCommand(cmd, rest);
123
+ break;
124
+ case '-h':
125
+ case '--help':
126
+ console.log(`norns <command>
127
+
128
+ Commands:
129
+ dev start vite dev with framework-source watching (default)
130
+ build run vite build
131
+ preview run vite preview
132
+ `);
133
+ break;
134
+ default:
135
+ console.error(`norns: unknown command "${cmd}"`);
136
+ process.exit(1);
137
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@human-synthesis/norns",
3
- "version": "0.0.5",
4
- "description": "Norns — SvelteKit with CoffeeScript, Pug, and UnoCSS preconfigured",
3
+ "version": "0.0.6",
4
+ "description": "Norns — SvelteKit with CoffeeScript, Pug, and the .n / .c file extensions",
5
5
  "license": "MIT",
6
- "author": "Human Synthesis",
6
+ "author": "Daniel Teodoroiu (https://humansynthesis.ai)",
7
7
  "type": "module",
8
8
  "repository": {
9
9
  "type": "git",
@@ -11,24 +11,26 @@
11
11
  },
12
12
  "files": [
13
13
  "src",
14
+ "bin",
14
15
  "README.md"
15
16
  ],
17
+ "bin": {
18
+ "norns": "./bin/norns.js"
19
+ },
16
20
  "exports": {
17
21
  ".": "./src/index.js",
18
22
  "./config": "./src/config.js",
19
23
  "./vite": "./src/vite.js",
20
24
  "./preprocess": "./src/preprocess.js",
21
- "./uno": "./src/uno.js",
22
25
  "./package.json": "./package.json"
23
26
  },
24
27
  "peerDependencies": {
25
28
  "@sveltejs/kit": "^2.0.0",
26
29
  "svelte": "^5.0.0",
27
- "unocss": "^0.65.0",
28
30
  "vite": "^5.0.0 || ^6.0.0"
29
31
  },
30
32
  "dependencies": {
31
- "@human-synthesis/norns-core": "^0.0.5",
33
+ "@human-synthesis/norns-core": "^0.0.6",
32
34
  "coffeescript": "^2.7.0"
33
35
  },
34
36
  "engines": {
package/src/config.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { nornsPreprocess } from '@human-synthesis/norns-core/preprocess';
2
2
 
3
3
  /**
4
- * Build a SvelteKit config preconfigured for Norns: Coffee/Pug preprocessing,
5
- * the `.norn` component file extension, and `.coffee` recognized as a
6
- * SvelteKit module extension (so files like `+page.coffee` and
7
- * `hooks.server.coffee` work).
4
+ * Build a SvelteKit config preconfigured for Norns.
5
+ *
6
+ * Defaults:
7
+ * - `extensions: ['.svelte', '.n']` both vanilla and Norns components
8
+ * - `kit.moduleExtensions: ['.js', '.ts', '.c']` — Kit special files (`+page.c` etc.)
9
+ * - `preprocess: nornsPreprocess()` — Coffee + Pug + rune fusion + auto-close
8
10
  *
9
11
  * Spread your own overrides at the call site to extend or replace defaults.
10
12
  *
@@ -19,10 +21,10 @@ export function nornsConfig(overrides = {}) {
19
21
  ...rest
20
22
  } = overrides;
21
23
  return {
22
- extensions: extensionsOverride ?? ['.svelte', '.norn'],
24
+ extensions: extensionsOverride ?? ['.svelte', '.n'],
23
25
  preprocess: preprocessOverride ?? nornsPreprocess(),
24
26
  kit: {
25
- moduleExtensions: ['.js', '.ts', '.coffee'],
27
+ moduleExtensions: ['.js', '.ts', '.c'],
26
28
  ...kitOverrides
27
29
  },
28
30
  ...rest
package/src/index.js CHANGED
@@ -1,4 +1,3 @@
1
1
  export { nornsConfig } from './config.js';
2
2
  export { nornsCoffeePlugin } from './vite.js';
3
3
  export { nornsPreprocess } from './preprocess.js';
4
- export { nornsUno } from './uno.js';
package/src/vite.js CHANGED
@@ -1,21 +1,128 @@
1
- import { readFile } from 'node:fs/promises';
1
+ import { readFile, stat, realpath } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { createRequire } from 'node:module';
2
4
  import CoffeeScript from 'coffeescript';
3
5
 
6
+ const DEFAULT_EXTENSIONS = ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'];
7
+ const NORNS_EXTENSIONS = ['.svelte', '.n', '.c'];
8
+ const RESOLVE_EXTENSIONS = [...NORNS_EXTENSIONS, '.ts', '.js'];
9
+ const FRAMEWORK_PKGS = ['@human-synthesis/norns-core', '@human-synthesis/norns'];
10
+
11
+ async function fileExists(path) {
12
+ try {
13
+ const s = await stat(path);
14
+ return s.isFile();
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Resolve framework package src dirs that live OUTSIDE the consuming app's
22
+ * node_modules (i.e. workspace symlinks pointing at sibling repos). Returns
23
+ * real on-disk paths to watch. Empty array when packages are installed
24
+ * normally — published consumers never enter this branch.
25
+ *
26
+ * @param {string} root
27
+ * @returns {Promise<string[]>}
28
+ */
29
+ async function resolveWorkspaceFrameworkSrcs(root) {
30
+ const require = createRequire(join(root, 'package.json'));
31
+ const out = [];
32
+ for (const pkg of FRAMEWORK_PKGS) {
33
+ try {
34
+ const pkgJsonPath = require.resolve(`${pkg}/package.json`);
35
+ const real = await realpath(pkgJsonPath);
36
+ const pkgDir = dirname(real);
37
+ // Only watch when the real path resolves outside any node_modules —
38
+ // that's the workspace-symlink case. A normal install resolves to
39
+ // a real path inside node_modules and we leave it alone.
40
+ if (!pkgDir.includes(`${join('/', 'node_modules', '/')}`)) {
41
+ out.push(join(pkgDir, 'src'));
42
+ }
43
+ } catch {
44
+ // Package not installed (e.g. only norns-core present) — skip.
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+
4
50
  /**
5
- * Vite plugin that compiles .coffee files to JavaScript.
6
- * Used so SvelteKit special files like +page.coffee, +page.server.coffee,
7
- * hooks.server.coffee, and +server.coffee work the same as their .js / .ts
8
- * counterparts.
51
+ * Vite plugin that:
52
+ * - compiles `.c` files (CoffeeScript) on the fly, so SvelteKit special
53
+ * files like `+page.c`, `+page.server.c`, `hooks.server.c`, and
54
+ * `+server.c` work the same as their `.js` / `.ts` counterparts;
55
+ * - registers `.svelte`, `.n`, and `.c` with Vite's resolver so bare
56
+ * imports (`import X from './Foo'`) try those extensions in priority
57
+ * order, on top of Vite's defaults;
58
+ * - resolves bare-name imports (`import X from 'Foo'`, no `./` prefix) to
59
+ * a sibling file when one exists, in the same priority order. Real
60
+ * package imports (`'svelte/store'`, `'@scope/pkg'`) are unaffected
61
+ * because they contain a slash or scope marker;
62
+ * - in workspace-linked dev (sibling repos symlinked into node_modules),
63
+ * excludes the framework packages from `optimizeDeps` pre-bundling and
64
+ * lifts them out of the default `**\/node_modules\/**` watch ignore so
65
+ * Vite reads source on each request and HMR fires. No-op for normal
66
+ * (published) installs. The companion `norns dev` CLI handles
67
+ * process-level respawn when framework source changes — needed because
68
+ * Node's ESM module cache survives `server.restart()`.
9
69
  *
10
70
  * @returns {import('vite').Plugin}
11
71
  */
12
72
  export function nornsCoffeePlugin() {
73
+ /** @type {string[]} */
74
+ let watchSrcs = [];
13
75
  return {
14
76
  name: 'norns:coffee',
15
77
  enforce: 'pre',
78
+ async config(_userConfig, { command }) {
79
+ if (command === 'serve') {
80
+ watchSrcs = await resolveWorkspaceFrameworkSrcs(process.cwd());
81
+ }
82
+ /** @type {import('vite').UserConfig} */
83
+ const cfg = {
84
+ resolve: {
85
+ extensions: [...DEFAULT_EXTENSIONS, ...NORNS_EXTENSIONS]
86
+ },
87
+ optimizeDeps: {
88
+ exclude: [...FRAMEWORK_PKGS]
89
+ }
90
+ };
91
+ if (watchSrcs.length > 0) {
92
+ cfg.server = {
93
+ fs: { allow: watchSrcs.map((p) => dirname(p)) },
94
+ watch: {
95
+ ignored: [
96
+ '**/.git/**',
97
+ (path) => {
98
+ if (watchSrcs.some((src) => path.startsWith(src))) return false;
99
+ return path.includes(`${join('/', 'node_modules', '/')}`);
100
+ }
101
+ ]
102
+ }
103
+ };
104
+ }
105
+ return cfg;
106
+ },
107
+ async resolveId(source, importer) {
108
+ if (!importer) return null;
109
+ // Already explicit relative or absolute — let the default resolver run.
110
+ if (source.startsWith('.') || source.startsWith('/')) return null;
111
+ // Scoped package or package-with-subpath — treat as a bare module.
112
+ if (source.startsWith('@') || source.includes('/')) return null;
113
+
114
+ // Single bare name — try resolving as a sibling file first, falling
115
+ // back to the default resolver (node_modules) if nothing matches.
116
+ const dir = dirname(importer);
117
+ for (const ext of RESOLVE_EXTENSIONS) {
118
+ const candidate = join(dir, source + ext);
119
+ if (await fileExists(candidate)) return candidate;
120
+ }
121
+ return null;
122
+ },
16
123
  async load(id) {
17
124
  const [path] = id.split('?');
18
- if (!path.endsWith('.coffee')) return null;
125
+ if (!path.endsWith('.c')) return null;
19
126
  const source = await readFile(path, 'utf8');
20
127
  const { js, sourceMap } = CoffeeScript.compile(source, {
21
128
  bare: true,
package/src/uno.js DELETED
@@ -1 +0,0 @@
1
- export { nornsUno } from '@human-synthesis/norns-core/uno';