niral 0.1.2 → 0.1.4
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 +109 -0
- package/bin/create-niral.js +5 -2
- package/bin/niral.js +2 -3
- package/index.js +179 -38
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Niral CLI
|
|
2
|
+
|
|
3
|
+
Zero-dependency launcher for the Niral full-stack framework. Requires Node.js 22+.
|
|
4
|
+
|
|
5
|
+
## Use
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx niral create myapp
|
|
9
|
+
cd myapp
|
|
10
|
+
npx niral dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or install one global launcher:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install -g niral
|
|
17
|
+
niral create myapp --template blog
|
|
18
|
+
niral dev myapp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Both `niral` and `create-niral` packages provide the same two executable names.
|
|
22
|
+
Install only one globally to avoid bin conflicts. Templates: `minimal` (default),
|
|
23
|
+
`blog`, `dashboard`. Create refuses to overwrite a nonempty project directory.
|
|
24
|
+
|
|
25
|
+
The first project command downloads the framework from the official
|
|
26
|
+
[repository](https://github.com/Zyora-Dev/niral) into `~/.niral/framework`.
|
|
27
|
+
Set `NIRAL_HOME` to choose another install directory. Normal commands reuse the
|
|
28
|
+
cached framework without checking the network. Keep projects outside that directory.
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
npx niral --help
|
|
34
|
+
npx niral --version
|
|
35
|
+
npx niral check myapp
|
|
36
|
+
npx niral test myapp
|
|
37
|
+
npx niral build myapp
|
|
38
|
+
npx niral start myapp
|
|
39
|
+
npx niral add api myapp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
All framework commands and arguments are forwarded, with the framework's exit
|
|
43
|
+
status preserved. See the [complete CLI reference](https://niral.site/docs/cli).
|
|
44
|
+
`--version` reports this launcher and, when installed, the framework version.
|
|
45
|
+
The generated direct `niral` shim reports the framework version only.
|
|
46
|
+
|
|
47
|
+
## Update
|
|
48
|
+
|
|
49
|
+
Launcher 0.1.4 supports explicit framework updates and persisted revision pins:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
npx niral@latest update
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This explicitly updates `NIRAL_HOME/framework`, even when the cached framework
|
|
56
|
+
has no `update` command. The default is the repository's `main` branch. Updating
|
|
57
|
+
also refreshes an existing generated shell shim so its updater remains independent
|
|
58
|
+
of older pinned framework code. Updating the npm launcher alone does not refresh
|
|
59
|
+
the framework; update a globally installed launcher separately with
|
|
60
|
+
`npm install -g niral@latest`.
|
|
61
|
+
|
|
62
|
+
Set `APPROVED_FRAMEWORK_SHA` to a reviewed full 40-character lowercase commit SHA:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
NIRAL_REF="$APPROVED_FRAMEWORK_SHA" npx niral@latest update
|
|
66
|
+
niral update
|
|
67
|
+
NIRAL_REF=main npx niral@latest update
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The selection is saved in `NIRAL_HOME/.framework-ref`; plain updates retain it.
|
|
71
|
+
Existing `v`-prefixed version tags are also accepted, but tags can move: use a
|
|
72
|
+
full commit SHA for reproducibility. `NIRAL_REF=main` explicitly removes the pin.
|
|
73
|
+
Fresh installations accept the same variable; switching a cached install requires
|
|
74
|
+
an explicit update. npm versions and framework revisions are independent.
|
|
75
|
+
|
|
76
|
+
Git installs must be clean, on `main` or a managed detached pin, and use the
|
|
77
|
+
official HTTPS origin. `main` updates
|
|
78
|
+
are fast-forward only; a Git failure never replaces the checkout with an archive.
|
|
79
|
+
Pin changes and non-Git replacements are staged and syntax-checked first; the complete previous
|
|
80
|
+
directory is retained at the printed `framework-backup-*/framework` path. Local
|
|
81
|
+
files in that backup are not merged into the new framework. No backups are pruned.
|
|
82
|
+
|
|
83
|
+
Stop active development/build commands before updating. Concurrent installers are
|
|
84
|
+
refused. If interrupted, inspect other installer processes before manually removing
|
|
85
|
+
a stale `NIRAL_HOME/.framework-install-lock` directory. Symlinked installs and
|
|
86
|
+
updates invoked from unmanaged source checkouts are refused; maintain those with
|
|
87
|
+
Git. An update does not migrate apps, publish packages or deploy/restart servers.
|
|
88
|
+
|
|
89
|
+
## Developer Intelligence
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
npx niral di setup
|
|
93
|
+
npx niral di status
|
|
94
|
+
npx niral di ask "Explain this project" --root ./myapp --read-only
|
|
95
|
+
npx niral di chat --root ./myapp
|
|
96
|
+
npx niral di new myblog "Customize the home page" --template blog
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`npx niral di setup` offers OpenAI, Claude (Anthropic), Azure OpenAI or Custom.
|
|
100
|
+
Named providers set protocol/auth automatically; Custom keeps manual choices.
|
|
101
|
+
Enter your exact model/deployment and credential variable name. Azure also asks
|
|
102
|
+
for its resource URL or `/openai/v1` base. Bedrock is not yet supported.
|
|
103
|
+
Use `npx niral di setup --help` for noninteractive flags.
|
|
104
|
+
Credentials stay in a named environment variable, not config or project files.
|
|
105
|
+
File reads/edits require a terminal and per-tool approval; writes retain backups
|
|
106
|
+
and reject stale hashes. `ask <prompt> --no-tools` permits prompt-only automation.
|
|
107
|
+
No unrestricted shell, automatic tests, transcript persistence or Niral Cloud
|
|
108
|
+
requirement. See the [DI commands and safeguards](https://niral.site/docs/cli).
|
|
109
|
+
Existing cached frameworks need `npx niral@latest update` to acquire DI commands.
|
package/bin/create-niral.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// npx create-niral my-app — the beginner's one command.
|
|
3
|
-
import { ensureFramework, installLauncher, runNiral } from "../index.js";
|
|
2
|
+
// npx create-niral my-app [--template blog|dashboard] — the beginner's one command.
|
|
3
|
+
import { ensureFramework, installLauncher, runNiral, runLauncher } from "../index.js";
|
|
4
4
|
|
|
5
5
|
const usage = "usage: npx create-niral <app-name|.> [--template minimal|blog|dashboard]";
|
|
6
6
|
|
|
@@ -9,8 +9,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
9
9
|
console.log(usage);
|
|
10
10
|
return 0;
|
|
11
11
|
}
|
|
12
|
+
if (argv.length === 1 && ["--version", "-v"].includes(argv[0])) return runLauncher(["--version"]);
|
|
12
13
|
const name = argv[0];
|
|
13
14
|
if (!name || (name.startsWith("-") && name !== ".")) throw new Error(usage);
|
|
15
|
+
if (argv.length !== 1 && !(argv.length === 3 && argv[1] === "--template" && ["minimal", "blog", "dashboard"].includes(argv[2]))) throw new Error(usage);
|
|
14
16
|
|
|
15
17
|
await ensureFramework();
|
|
16
18
|
const code = runNiral(["create", name, ...argv.slice(1)]);
|
|
@@ -19,6 +21,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
19
21
|
const { profile, added } = installLauncher();
|
|
20
22
|
console.log("");
|
|
21
23
|
if (added) console.log(`✓ \`niral\` command installed — PATH updated in ${profile} (open a new terminal)`);
|
|
24
|
+
if (!profile) console.log("Use npx niral <command>, or add NIRAL_HOME/bin to your PATH.");
|
|
22
25
|
const cd = name === "." ? "" : ` cd ${name}\n`;
|
|
23
26
|
console.log(`Next:\n${cd} niral dev # or: npx niral dev\n`);
|
|
24
27
|
return 0;
|
package/bin/niral.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// niral <command> — forwards to the framework at ~/.niral/framework.
|
|
3
3
|
// Available globally after `npm i -g create-niral`, or per-call via `npx niral dev`.
|
|
4
|
-
import {
|
|
4
|
+
import { runLauncher } from "../index.js";
|
|
5
5
|
|
|
6
6
|
async function main() {
|
|
7
|
-
|
|
8
|
-
return runNiral(process.argv.slice(2));
|
|
7
|
+
return runLauncher(process.argv.slice(2));
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
main().then((code) => {
|
package/index.js
CHANGED
|
@@ -6,29 +6,45 @@
|
|
|
6
6
|
* forwards commands to it. The framework itself never touches npm.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync, chmodSync, appendFileSync, readFileSync } from "node:fs";
|
|
9
|
+
import { existsSync, mkdirSync, mkdtempSync, renameSync, lstatSync, realpathSync, rmSync, writeFileSync, chmodSync, appendFileSync, readFileSync } from "node:fs";
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
11
|
-
import { join } from "node:path";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
import { get } from "node:https";
|
|
14
14
|
|
|
15
|
-
export const NIRAL_HOME = process.env.NIRAL_HOME || join(homedir(), ".niral");
|
|
15
|
+
export const NIRAL_HOME = resolve(process.env.NIRAL_HOME || join(homedir(), ".niral"));
|
|
16
16
|
const FRAMEWORK = join(NIRAL_HOME, "framework");
|
|
17
|
-
const TARBALL = "https://codeload.github.com/Zyora-Dev/niral/tar.gz/refs/heads/main";
|
|
18
17
|
const REPO = "https://github.com/Zyora-Dev/niral.git";
|
|
19
18
|
|
|
20
19
|
function download(url, dest) {
|
|
21
20
|
return new Promise((resolve, reject) => {
|
|
22
|
-
get(url, (res) => {
|
|
23
|
-
if (res.statusCode !== 200)
|
|
21
|
+
const request = get(url, (res) => {
|
|
22
|
+
if (res.statusCode !== 200) {
|
|
23
|
+
res.resume();
|
|
24
|
+
return reject(new Error(`download failed: HTTP ${res.statusCode}`));
|
|
25
|
+
}
|
|
24
26
|
const chunks = [];
|
|
25
|
-
|
|
27
|
+
let bytes = 0;
|
|
28
|
+
res.on("data", (chunk) => {
|
|
29
|
+
bytes += chunk.length;
|
|
30
|
+
if (bytes > 64 * 1024 * 1024) {
|
|
31
|
+
request.destroy(new Error("framework download exceeds 64 MiB"));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
chunks.push(chunk);
|
|
35
|
+
});
|
|
26
36
|
res.on("end", () => {
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
try {
|
|
38
|
+
writeFileSync(dest, Buffer.concat(chunks));
|
|
39
|
+
resolve();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
reject(error);
|
|
42
|
+
}
|
|
29
43
|
});
|
|
30
44
|
res.on("error", reject);
|
|
31
45
|
}).on("error", reject);
|
|
46
|
+
const deadline = setTimeout(() => request.destroy(new Error("framework download timed out")), 60000);
|
|
47
|
+
request.on("close", () => clearTimeout(deadline));
|
|
32
48
|
});
|
|
33
49
|
}
|
|
34
50
|
|
|
@@ -42,51 +58,174 @@ function commandError(label, result) {
|
|
|
42
58
|
return new Error(`${label} failed: ${detail}`);
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
async function installFromTarball() {
|
|
46
|
-
const tgz = join(
|
|
47
|
-
await download(
|
|
48
|
-
|
|
49
|
-
mkdirSync(
|
|
50
|
-
const extracted = spawnSync("tar", ["-xzf", tgz, "-C",
|
|
61
|
+
async function installFromTarball(stage, ref) {
|
|
62
|
+
const tgz = join(stage, "niral.tgz");
|
|
63
|
+
await download(`https://codeload.github.com/Zyora-Dev/niral/tar.gz/${ref === "main" ? "refs/heads/main" : encodeURIComponent(ref)}`, tgz);
|
|
64
|
+
const target = join(stage, "framework");
|
|
65
|
+
mkdirSync(target);
|
|
66
|
+
const extracted = spawnSync("tar", ["-xzf", tgz, "-C", target, "--strip-components=1"], { encoding: "utf8", timeout: 120000 });
|
|
51
67
|
rmSync(tgz, { force: true });
|
|
52
68
|
if (extracted.error || extracted.status !== 0) throw commandError("framework extraction", extracted);
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
/** Download the framework to ~/.niral/framework (git if available, tarball otherwise). */
|
|
56
72
|
export async function ensureFramework({ update = false } = {}) {
|
|
57
|
-
if (
|
|
58
|
-
|
|
73
|
+
if (Number(process.versions.node.split(".")[0]) < 22) throw new Error("Niral requires Node.js 22 or newer");
|
|
74
|
+
const pinFile = join(NIRAL_HOME, ".framework-ref");
|
|
75
|
+
const previousRef = existsSync(pinFile) ? readFileSync(pinFile, "utf8").trim() : "main";
|
|
76
|
+
const ref = process.env.NIRAL_REF ?? previousRef;
|
|
77
|
+
if (!/^(?:main|[a-f0-9]{40}|v\d+\.\d+\.\d+(?:[-.][A-Za-z0-9.-]+)?)$/.test(ref)) throw new Error("NIRAL_REF must be main, a full lowercase commit SHA, or a v-prefixed version tag");
|
|
78
|
+
if (existsSync(join(FRAMEWORK, "bin", "niral.js")) && !update) {
|
|
79
|
+
if (ref !== previousRef) throw new Error("framework pin differs from the installed version; run niral update with NIRAL_REF set");
|
|
80
|
+
return FRAMEWORK;
|
|
81
|
+
}
|
|
59
82
|
mkdirSync(NIRAL_HOME, { recursive: true });
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
const lock = join(NIRAL_HOME, ".framework-install-lock");
|
|
84
|
+
try {
|
|
85
|
+
mkdirSync(lock);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error.code === "EEXIST") throw new Error(`framework installation is locked: ${lock}. Check for another installer before removing a stale lock.`);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
let stage;
|
|
91
|
+
try {
|
|
92
|
+
if (existsSync(join(FRAMEWORK, "bin", "niral.js")) && !update) return FRAMEWORK;
|
|
93
|
+
const entry = lstatSync(FRAMEWORK, { throwIfNoEntry: false });
|
|
94
|
+
if (entry && (!entry.isDirectory() || entry.isSymbolicLink())) throw new Error("framework path must be a real directory; installation left unchanged");
|
|
95
|
+
console.log(update ? "niral: updating the installed framework..." : "niral: downloading the framework...");
|
|
96
|
+
if (existsSync(join(FRAMEWORK, ".git"))) {
|
|
97
|
+
if (!hasGit()) throw new Error("Git is required to update this checkout; installation left unchanged");
|
|
98
|
+
const git = (args) => {
|
|
99
|
+
const result = spawnSync("git", args, { cwd: FRAMEWORK, encoding: "utf8", timeout: 120000, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } });
|
|
100
|
+
if (result.error || result.status !== 0) throw commandError("Git update (no archive fallback)", result);
|
|
101
|
+
return result.stdout.trim();
|
|
102
|
+
};
|
|
103
|
+
if (git(["status", "--porcelain", "--untracked-files=all"])) throw new Error("framework checkout has local changes; preserve them before updating");
|
|
104
|
+
const branch = git(["branch", "--show-current"]);
|
|
105
|
+
if (branch !== "main" && !(previousRef !== "main" && branch === "")) throw new Error("framework checkout is not on main or a managed pin; update it manually with Git");
|
|
106
|
+
if (![REPO, REPO.replace(/\.git$/, "")].includes(git(["remote", "get-url", "origin"]))) throw new Error("framework origin differs from the official repository; update it manually with Git");
|
|
107
|
+
if (ref === "main" && previousRef === "main") {
|
|
108
|
+
const previous = git(["rev-parse", "HEAD"]);
|
|
109
|
+
git(["pull", "--ff-only", "origin", "main"]);
|
|
110
|
+
console.log(`niral: framework ${git(["rev-parse", "--short", "HEAD"])} (previous ${previous})`);
|
|
111
|
+
return FRAMEWORK;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
stage = mkdtempSync(join(NIRAL_HOME, ".framework-stage-"));
|
|
115
|
+
const target = join(stage, "framework");
|
|
116
|
+
let installed = false;
|
|
117
|
+
if (hasGit()) {
|
|
118
|
+
const args = ref === "main" ? ["clone", "-q", "--depth", "1", "--branch", "main", REPO, target] : ["clone", "-q", "--no-checkout", REPO, target];
|
|
119
|
+
const cloned = spawnSync("git", args, {
|
|
120
|
+
encoding: "utf8", timeout: 120000, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
121
|
+
});
|
|
122
|
+
installed = !cloned.error && cloned.status === 0;
|
|
123
|
+
if (installed && ref !== "main") {
|
|
124
|
+
const checkedOut = spawnSync("git", ["checkout", "--detach", ref], { cwd: target, encoding: "utf8", timeout: 120000, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } });
|
|
125
|
+
if (checkedOut.error || checkedOut.status !== 0) throw commandError("pinned framework checkout", checkedOut);
|
|
126
|
+
if (/^[a-f0-9]{40}$/.test(ref)) {
|
|
127
|
+
const revision = spawnSync("git", ["rev-parse", "HEAD"], { cwd: target, encoding: "utf8", timeout: 10000 });
|
|
128
|
+
if (revision.status !== 0 || revision.stdout.trim() !== ref) throw new Error("pinned framework revision did not match the requested commit");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!installed) {
|
|
132
|
+
rmSync(target, { recursive: true, force: true });
|
|
133
|
+
console.warn("niral: Git download unavailable; trying the framework archive...");
|
|
134
|
+
}
|
|
73
135
|
}
|
|
136
|
+
if (!installed) await installFromTarball(stage, ref);
|
|
137
|
+
const manifest = JSON.parse(readFileSync(join(target, "package.json"), "utf8"));
|
|
138
|
+
if (manifest.name !== "niral" || !existsSync(join(target, "src", "index.js"))) throw new Error("download is not a Niral framework");
|
|
139
|
+
const checked = spawnSync(process.execPath, ["--check", join(target, "bin", "niral.js")], { encoding: "utf8", timeout: 10000 });
|
|
140
|
+
if (checked.error || checked.status !== 0) throw commandError("downloaded CLI validation", checked);
|
|
141
|
+
const stagedPin = join(stage, "ref");
|
|
142
|
+
if (ref !== "main") writeFileSync(stagedPin, ref + "\n");
|
|
143
|
+
let backup;
|
|
144
|
+
if (entry) {
|
|
145
|
+
backup = join(mkdtempSync(join(NIRAL_HOME, "framework-backup-")), "framework");
|
|
146
|
+
renameSync(FRAMEWORK, backup);
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
renameSync(target, FRAMEWORK);
|
|
150
|
+
if (ref === "main") rmSync(pinFile, { force: true });
|
|
151
|
+
else renameSync(stagedPin, pinFile);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (existsSync(FRAMEWORK)) renameSync(FRAMEWORK, target);
|
|
154
|
+
if (backup) renameSync(backup, FRAMEWORK);
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
if (backup) console.log(`niral: previous framework preserved at ${backup}`);
|
|
158
|
+
console.log(`niral: framework ${manifest.version} ready (${ref})`);
|
|
159
|
+
return FRAMEWORK;
|
|
160
|
+
} finally {
|
|
161
|
+
if (stage) rmSync(stage, { recursive: true, force: true });
|
|
162
|
+
rmSync(lock, { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function updateFramework({ sourceRoot } = {}) {
|
|
167
|
+
if (sourceRoot && (!existsSync(FRAMEWORK) || realpathSync(sourceRoot) !== realpathSync(FRAMEWORK))) {
|
|
168
|
+
throw new Error("niral update only updates the managed NIRAL_HOME/framework installation. Update source checkouts with Git; deploy servers separately.");
|
|
74
169
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
170
|
+
await ensureFramework({ update: true });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function runLauncher(args) {
|
|
174
|
+
if (args[0] === "update") {
|
|
175
|
+
if (args.length === 2 && ["--help", "-h"].includes(args[1])) {
|
|
176
|
+
console.log("usage: niral update\nUpdates NIRAL_HOME/framework using its saved NIRAL_REF (default main); does not update npm packages or deploy apps.");
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
if (args.length !== 1) throw new Error("usage: niral update");
|
|
180
|
+
await updateFramework();
|
|
181
|
+
if (existsSync(join(NIRAL_HOME, "bin", process.platform === "win32" ? "niral.cmd" : "niral"))) installLauncher();
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
if (!args.length || ["help", "--help", "-h"].includes(args[0])) {
|
|
185
|
+
if (existsSync(join(FRAMEWORK, "bin", "niral.js"))) return runNiral(args);
|
|
186
|
+
console.log("niral <command> [options]\nCommands: create, dev, build, start, check, test, add, doctor, di, update.\nThe first framework command downloads the framework.\nniral di --help: Developer Intelligence setup, status, ask, chat, new.\nniral update: explicitly refresh the installed framework.\nRun niral --help after installation for the full framework command list.");
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
if (["--version", "-v", "version"].includes(args[0])) {
|
|
190
|
+
const manifest = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8"));
|
|
191
|
+
console.log(`${manifest.name} launcher ${manifest.version}`);
|
|
192
|
+
if (existsSync(join(FRAMEWORK, "package.json"))) {
|
|
193
|
+
const framework = JSON.parse(readFileSync(join(FRAMEWORK, "package.json"), "utf8"));
|
|
194
|
+
console.log(`niral framework ${framework.version}`);
|
|
195
|
+
}
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
await ensureFramework();
|
|
199
|
+
return runNiral(args);
|
|
78
200
|
}
|
|
79
201
|
|
|
80
202
|
/** Put a `niral` launcher on the PATH so every later command is just `niral dev`. */
|
|
81
203
|
export function installLauncher() {
|
|
82
204
|
const binDir = join(NIRAL_HOME, "bin");
|
|
83
205
|
mkdirSync(binDir, { recursive: true });
|
|
84
|
-
const
|
|
206
|
+
const launcherDir = join(NIRAL_HOME, "launcher");
|
|
207
|
+
mkdirSync(launcherDir, { recursive: true });
|
|
208
|
+
writeFileSync(join(launcherDir, "index.mjs"), readFileSync(new URL(import.meta.url)));
|
|
209
|
+
const frameworkCli = join(launcherDir, "run.mjs");
|
|
210
|
+
writeFileSync(frameworkCli, `import { ensureFramework, runLauncher, runNiral } from "./index.mjs";
|
|
211
|
+
try {
|
|
212
|
+
const args = process.argv.slice(2);
|
|
213
|
+
if (args[0] === "update") process.exitCode = await runLauncher(args);
|
|
214
|
+
else {
|
|
215
|
+
await ensureFramework();
|
|
216
|
+
process.exitCode = runNiral(args);
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error(error.message);
|
|
220
|
+
process.exitCode = 1;
|
|
221
|
+
}
|
|
222
|
+
`);
|
|
85
223
|
const shim = join(binDir, process.platform === "win32" ? "niral.cmd" : "niral");
|
|
86
224
|
if (process.platform === "win32") {
|
|
87
|
-
writeFileSync(shim, `@echo off\r\n"${process.execPath}" "${frameworkCli}" %*\r\n`);
|
|
225
|
+
writeFileSync(shim, `@echo off\r\nset "NIRAL_HOME=${NIRAL_HOME.replaceAll("%", "%%")}"\r\n"${process.execPath.replaceAll("%", "%%")}" "${frameworkCli.replaceAll("%", "%%")}" %*\r\n`);
|
|
88
226
|
} else {
|
|
89
|
-
|
|
227
|
+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
228
|
+
writeFileSync(shim, `#!/usr/bin/env sh\nexport NIRAL_HOME=${quote(NIRAL_HOME)}\nexec ${quote(process.execPath)} ${quote(frameworkCli)} "$@"\n`);
|
|
90
229
|
chmodSync(shim, 0o755);
|
|
91
230
|
}
|
|
92
231
|
|
|
@@ -95,9 +234,11 @@ export function installLauncher() {
|
|
|
95
234
|
: (process.env.SHELL ?? "").endsWith("/bash") ? join(homedir(), ".bashrc")
|
|
96
235
|
: null;
|
|
97
236
|
if (profile) {
|
|
98
|
-
const line =
|
|
237
|
+
const line = NIRAL_HOME === join(homedir(), ".niral")
|
|
238
|
+
? `export PATH="$HOME/.niral/bin:$PATH"`
|
|
239
|
+
: `export PATH='${binDir.replaceAll("'", "'\\''")}':"$PATH"`;
|
|
99
240
|
const current = existsSync(profile) ? readFileSync(profile, "utf8") : "";
|
|
100
|
-
if (!current.
|
|
241
|
+
if (!current.split(/\r?\n/).includes(line)) {
|
|
101
242
|
appendFileSync(profile, `\n# niral\n${line}\n`);
|
|
102
243
|
return { shim, profile, added: true };
|
|
103
244
|
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "niral",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Niral — the zero-dependency full-stack framework by ZyoraLabs. This package is a tiny launcher; the framework itself lives at github.com/Zyora-Dev/niral and has zero dependencies.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"niral": "bin/niral.js",
|
|
7
7
|
"create-niral": "bin/create-niral.js"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
|
+
"files": ["bin/", "index.js", "README.md"],
|
|
10
11
|
"engines": { "node": ">=22" },
|
|
11
12
|
"keywords": ["niral", "framework", "fullstack", "zero-dependencies", "ssr", "zyoralabs"],
|
|
12
|
-
"homepage": "https://niral.
|
|
13
|
+
"homepage": "https://niral.site",
|
|
13
14
|
"repository": { "type": "git", "url": "git+https://github.com/Zyora-Dev/niral.git" },
|
|
14
15
|
"license": "MIT",
|
|
15
16
|
"dependencies": {}
|