niral 0.1.3 → 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.
Files changed (3) hide show
  1. package/README.md +29 -10
  2. package/index.js +54 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -46,22 +46,37 @@ The generated direct `niral` shim reports the framework version only.
46
46
 
47
47
  ## Update
48
48
 
49
- Launcher 0.1.3 supports explicit framework updates:
49
+ Launcher 0.1.4 supports explicit framework updates and persisted revision pins:
50
50
 
51
51
  ```sh
52
52
  npx niral@latest update
53
53
  ```
54
54
 
55
- This explicitly updates `NIRAL_HOME/framework` from the repository's `main`
56
- branch, even when the cached framework has no `update` command. After updating,
57
- the direct `niral update` command is available too. Updates follow `main`, not
58
- a version-pinned release. Updating the npm launcher alone does not refresh the
59
- framework; update a globally installed launcher separately with
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
60
  `npm install -g niral@latest`.
61
61
 
62
- Git installs must be clean, on `main`, and use the official HTTPS origin. Updates
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
63
78
  are fast-forward only; a Git failure never replaces the checkout with an archive.
64
- Non-Git replacements are staged and syntax-checked first; the complete previous
79
+ Pin changes and non-Git replacements are staged and syntax-checked first; the complete previous
65
80
  directory is retained at the printed `framework-backup-*/framework` path. Local
66
81
  files in that backup are not merged into the new framework. No backups are pruned.
67
82
 
@@ -74,14 +89,18 @@ Git. An update does not migrate apps, publish packages or deploy/restart servers
74
89
  ## Developer Intelligence
75
90
 
76
91
  ```sh
77
- npx niral di setup --help
92
+ npx niral di setup
78
93
  npx niral di status
79
94
  npx niral di ask "Explain this project" --root ./myapp --read-only
80
95
  npx niral di chat --root ./myapp
81
96
  npx niral di new myblog "Customize the home page" --template blog
82
97
  ```
83
98
 
84
- Configure your exact protocol, API base, model/deployment and auth with `di setup`.
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.
85
104
  Credentials stay in a named environment variable, not config or project files.
86
105
  File reads/edits require a terminal and per-tool approval; writes retain backups
87
106
  and reject stale hashes. `ask <prompt> --no-tools` permits prompt-only automation.
package/index.js CHANGED
@@ -14,7 +14,6 @@ import { get } from "node:https";
14
14
 
15
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) {
@@ -59,9 +58,9 @@ function commandError(label, result) {
59
58
  return new Error(`${label} failed: ${detail}`);
60
59
  }
61
60
 
62
- async function installFromTarball(stage) {
61
+ async function installFromTarball(stage, ref) {
63
62
  const tgz = join(stage, "niral.tgz");
64
- await download(TARBALL, tgz);
63
+ await download(`https://codeload.github.com/Zyora-Dev/niral/tar.gz/${ref === "main" ? "refs/heads/main" : encodeURIComponent(ref)}`, tgz);
65
64
  const target = join(stage, "framework");
66
65
  mkdirSync(target);
67
66
  const extracted = spawnSync("tar", ["-xzf", tgz, "-C", target, "--strip-components=1"], { encoding: "utf8", timeout: 120000 });
@@ -72,7 +71,14 @@ async function installFromTarball(stage) {
72
71
  /** Download the framework to ~/.niral/framework (git if available, tarball otherwise). */
73
72
  export async function ensureFramework({ update = false } = {}) {
74
73
  if (Number(process.versions.node.split(".")[0]) < 22) throw new Error("Niral requires Node.js 22 or newer");
75
- if (existsSync(join(FRAMEWORK, "bin", "niral.js")) && !update) return FRAMEWORK;
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
+ }
76
82
  mkdirSync(NIRAL_HOME, { recursive: true });
77
83
  const lock = join(NIRAL_HOME, ".framework-install-lock");
78
84
  try {
@@ -95,31 +101,45 @@ export async function ensureFramework({ update = false } = {}) {
95
101
  return result.stdout.trim();
96
102
  };
97
103
  if (git(["status", "--porcelain", "--untracked-files=all"])) throw new Error("framework checkout has local changes; preserve them before updating");
98
- if (git(["branch", "--show-current"]) !== "main") throw new Error("framework checkout is not on main; update it manually with Git");
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");
99
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");
100
- const previous = git(["rev-parse", "HEAD"]);
101
- git(["pull", "--ff-only", "origin", "main"]);
102
- console.log(`niral: framework ${git(["rev-parse", "--short", "HEAD"])} (previous ${previous})`);
103
- return FRAMEWORK;
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
+ }
104
113
  }
105
114
  stage = mkdtempSync(join(NIRAL_HOME, ".framework-stage-"));
106
115
  const target = join(stage, "framework");
107
116
  let installed = false;
108
117
  if (hasGit()) {
109
- const cloned = spawnSync("git", ["clone", "-q", "--depth", "1", "--branch", "main", REPO, target], {
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, {
110
120
  encoding: "utf8", timeout: 120000, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
111
121
  });
112
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
+ }
113
131
  if (!installed) {
114
132
  rmSync(target, { recursive: true, force: true });
115
133
  console.warn("niral: Git download unavailable; trying the framework archive...");
116
134
  }
117
135
  }
118
- if (!installed) await installFromTarball(stage);
136
+ if (!installed) await installFromTarball(stage, ref);
119
137
  const manifest = JSON.parse(readFileSync(join(target, "package.json"), "utf8"));
120
138
  if (manifest.name !== "niral" || !existsSync(join(target, "src", "index.js"))) throw new Error("download is not a Niral framework");
121
139
  const checked = spawnSync(process.execPath, ["--check", join(target, "bin", "niral.js")], { encoding: "utf8", timeout: 10000 });
122
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");
123
143
  let backup;
124
144
  if (entry) {
125
145
  backup = join(mkdtempSync(join(NIRAL_HOME, "framework-backup-")), "framework");
@@ -127,12 +147,15 @@ export async function ensureFramework({ update = false } = {}) {
127
147
  }
128
148
  try {
129
149
  renameSync(target, FRAMEWORK);
150
+ if (ref === "main") rmSync(pinFile, { force: true });
151
+ else renameSync(stagedPin, pinFile);
130
152
  } catch (error) {
153
+ if (existsSync(FRAMEWORK)) renameSync(FRAMEWORK, target);
131
154
  if (backup) renameSync(backup, FRAMEWORK);
132
155
  throw error;
133
156
  }
134
157
  if (backup) console.log(`niral: previous framework preserved at ${backup}`);
135
- console.log(`niral: framework ${manifest.version} ready`);
158
+ console.log(`niral: framework ${manifest.version} ready (${ref})`);
136
159
  return FRAMEWORK;
137
160
  } finally {
138
161
  if (stage) rmSync(stage, { recursive: true, force: true });
@@ -150,11 +173,12 @@ export async function updateFramework({ sourceRoot } = {}) {
150
173
  export async function runLauncher(args) {
151
174
  if (args[0] === "update") {
152
175
  if (args.length === 2 && ["--help", "-h"].includes(args[1])) {
153
- console.log("usage: niral update\nUpdates NIRAL_HOME/framework from main; does not update npm packages or deploy apps.");
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.");
154
177
  return 0;
155
178
  }
156
179
  if (args.length !== 1) throw new Error("usage: niral update");
157
180
  await updateFramework();
181
+ if (existsSync(join(NIRAL_HOME, "bin", process.platform === "win32" ? "niral.cmd" : "niral"))) installLauncher();
158
182
  return 0;
159
183
  }
160
184
  if (!args.length || ["help", "--help", "-h"].includes(args[0])) {
@@ -179,7 +203,23 @@ export async function runLauncher(args) {
179
203
  export function installLauncher() {
180
204
  const binDir = join(NIRAL_HOME, "bin");
181
205
  mkdirSync(binDir, { recursive: true });
182
- const frameworkCli = join(FRAMEWORK, "bin", "niral.js");
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
+ `);
183
223
  const shim = join(binDir, process.platform === "win32" ? "niral.cmd" : "niral");
184
224
  if (process.platform === "win32") {
185
225
  writeFileSync(shim, `@echo off\r\nset "NIRAL_HOME=${NIRAL_HOME.replaceAll("%", "%%")}"\r\n"${process.execPath.replaceAll("%", "%%")}" "${frameworkCli.replaceAll("%", "%%")}" %*\r\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niral",
3
- "version": "0.1.3",
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",