@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 +1 -0
- package/package.json +1 -1
- package/src/cli/create.ts +1 -1
- package/src/cli/index.ts +6 -2
- package/src/cli/update.ts +106 -0
- package/src/modules/surreal.ts +10 -2
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.
|
|
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
|
+
`);
|
package/src/modules/surreal.ts
CHANGED
|
@@ -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);
|