@njinlabs/njin 0.4.0 → 0.5.1

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 CHANGED
@@ -398,6 +398,7 @@ bunx @njinlabs/njin create my-web # Scaffold a new project
398
398
  bunx njin dev # Start dev server (Elysia + Vite HMR)
399
399
  bunx njin build # Build for production -> ./out (public/, _admin/, views/, compiled server)
400
400
  bunx njin start # Run from source in production mode (no compile)
401
+ bunx njin update # Update @njinlabs/njin to the latest version and refresh _admin/
401
402
  ```
402
403
 
403
404
  Wire these as `package.json` scripts (`"dev": "njin dev"`, etc.) if you'd rather run `bun run dev`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@njinlabs/njin",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "A modern framework for building company profiles, landing pages, and content-driven websites.",
5
5
  "type": "module",
6
6
  "keywords": ["bun", "elysia", "surrealdb", "edgejs", "cms", "framework"],
package/src/cli/create.ts CHANGED
@@ -35,7 +35,7 @@ mkdirSync(targetDir, { recursive: true });
35
35
 
36
36
  // `--strip-components=2` drops the `njin-<version>/template/` prefix baked into
37
37
  // GitHub's tag-tarball layout, so template/ contents land directly in targetDir.
38
- const tar = Bun.spawn(["tar", "-xz", "--strip-components=2", "-C", targetDir, `njin-${pkg.version}/template`], {
38
+ const tar = Bun.spawn(["tar", "-xz", "-f", "-", "--strip-components=2", "-C", targetDir, `njin-${pkg.version}/template`], {
39
39
  stdin: res.body,
40
40
  stdout: "inherit",
41
41
  stderr: "inherit",
package/src/cli/index.ts CHANGED
@@ -14,12 +14,16 @@ switch (command) {
14
14
  case "build":
15
15
  await import("./build");
16
16
  break;
17
+ case "update":
18
+ await import("./update");
19
+ break;
17
20
  default:
18
- console.log(`Usage: njin <create|dev|build|start>
21
+ console.log(`Usage: njin <create|dev|build|start|update>
19
22
 
20
23
  create <dir> Scaffold a new project (defaults to current directory)
21
24
  dev Run the dev server (Vite HMR + live reload)
22
25
  build Build for production -> ./out (public/, _admin/, views/, server)
23
- start Run from source in production mode (no compile)`);
26
+ start Run from source in production mode (no compile)
27
+ update Update @njinlabs/njin to the latest version and refresh _admin/`);
24
28
  process.exit(command ? 1 : 0);
25
29
  }
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, rm } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+
6
+ const c = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", green: "\x1b[32m" };
7
+
8
+ const root = process.cwd();
9
+ const pkgPath = join(root, "package.json");
10
+
11
+ if (!existsSync(pkgPath)) {
12
+ console.error(`✖ No package.json found in ${root} — run this from an njin project root.`);
13
+ process.exit(1);
14
+ }
15
+
16
+ const pkg = await Bun.file(pkgPath).json();
17
+ const depKey = pkg.dependencies?.["@njinlabs/njin"]
18
+ ? "dependencies"
19
+ : pkg.devDependencies?.["@njinlabs/njin"]
20
+ ? "devDependencies"
21
+ : undefined;
22
+
23
+ if (!depKey) {
24
+ console.error(`✖ "@njinlabs/njin" is not listed in this project's package.json — run this from an njin project root.`);
25
+ process.exit(1);
26
+ }
27
+
28
+ const currentPinned = String(pkg[depKey]["@njinlabs/njin"]).replace(/^[\^~]/, "");
29
+
30
+ console.log("Checking latest njin version...\n");
31
+
32
+ const registryRes = await fetch("https://registry.npmjs.org/@njinlabs/njin/latest");
33
+ if (!registryRes.ok) {
34
+ console.error(`✖ Could not reach the npm registry (HTTP ${registryRes.status}).`);
35
+ process.exit(1);
36
+ }
37
+
38
+ const { version: latest } = (await registryRes.json()) as { version: string };
39
+ const adminDir = join(root, "_admin");
40
+
41
+ if (currentPinned === latest && existsSync(adminDir)) {
42
+ console.log(`${c.green}✓${c.reset} Already on the latest version (${latest}) — nothing to update.`);
43
+ process.exit(0);
44
+ }
45
+
46
+ console.log(`Updating njin ${currentPinned} -> ${latest}...\n`);
47
+
48
+ pkg[depKey]["@njinlabs/njin"] = `^${latest}`;
49
+ await Bun.write(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
50
+
51
+ console.log("Installing dependencies...\n");
52
+
53
+ const install = Bun.spawn(["bun", "install"], { cwd: root, stdout: "inherit", stderr: "inherit" });
54
+ const installExitCode = await install.exited;
55
+
56
+ if (installExitCode !== 0) {
57
+ console.error(`\n✖ "bun install" failed (exit code ${installExitCode}).`);
58
+ console.error(` package.json now points at njin ${latest}; run "bun install" manually to retry.`);
59
+ process.exit(installExitCode);
60
+ }
61
+
62
+ console.log("\nSyncing admin panel...\n");
63
+
64
+ // _admin/ is a prebuilt SPA baked into the tagged release tarball, not something published to
65
+ // npm (package.json's "files" excludes template/) — so bumping the dependency above never
66
+ // touches it. It's rebuilt with content-hashed asset filenames on every release, so the old
67
+ // directory is removed first rather than merged, to avoid accumulating stale hashed files.
68
+ const tag = `v${latest}`;
69
+ const tarballUrl = `https://codeload.github.com/njinlabs/njin/tar.gz/refs/tags/${tag}`;
70
+
71
+ const res = await fetch(tarballUrl);
72
+ if (!res.ok || !res.body) {
73
+ console.error(
74
+ `✖ Could not download the admin panel for njin ${tag} (HTTP ${res.status}).\n` +
75
+ ` The dependency was updated to ${latest}, but _admin/ was not refreshed. Re-run "bunx njin update" to retry.`,
76
+ );
77
+ process.exit(1);
78
+ }
79
+
80
+ await rm(adminDir, { recursive: true, force: true });
81
+ await mkdir(adminDir, { recursive: true });
82
+
83
+ // --strip-components=3 drops the `njin-<version>/template/_admin/` prefix baked into GitHub's
84
+ // tag-tarball layout, so _admin/'s own contents land directly in adminDir.
85
+ const tar = Bun.spawn(["tar", "-xz", "-f", "-", "--strip-components=3", "-C", adminDir, `njin-${latest}/template/_admin`], {
86
+ stdin: res.body,
87
+ stdout: "inherit",
88
+ stderr: "inherit",
89
+ });
90
+
91
+ const tarExitCode = await tar.exited;
92
+
93
+ if (tarExitCode !== 0) {
94
+ console.error(
95
+ `✖ Failed to extract the admin panel (tar exited with code ${tarExitCode}).\n` +
96
+ ` The dependency was updated to ${latest}, but _admin/ was not refreshed.`,
97
+ );
98
+ process.exit(tarExitCode);
99
+ }
100
+
101
+ console.log(`
102
+ ${c.bold}${c.cyan}njin update${c.reset} ${c.dim}done${c.reset}
103
+
104
+ ${c.green}➜${c.reset} njin ${currentPinned} -> ${latest}
105
+ ${c.green}➜${c.reset} _admin/ refreshed
106
+ `);
@@ -62,10 +62,18 @@ const surreal = makeModule(() => {
62
62
  db = new Surreal({ engines });
63
63
  await db.connect(dbConfig.path, { authentication: dbConfig.auth });
64
64
  await db.use({ namespace: dbConfig.namespace, database: dbConfig.database });
65
- await ensureTables(db);
66
65
 
67
66
  return {
68
- spin: () => {
67
+ // Deferred to spin(), not run here — ensureTables() imports every registered model
68
+ // to read its prefix, which forces evaluation of data types like file()/multiFile()
69
+ // that depend on other modules' singletons (e.g. fileModule().model, only set once
70
+ // file.init() has run). Every module's init() — file's included — has already
71
+ // completed by the time any module's spin() starts, so this ordering is safe. It
72
+ // still runs before elysia's spin() (which calls app.listen()), since surreal is
73
+ // earlier in src/config/module.ts's array, so tables exist before the first request.
74
+ spin: async () => {
75
+ await ensureTables(db);
76
+
69
77
  const shutdown = async () => {
70
78
  await db.close();
71
79
  process.exit(0);