neoctl 0.2.12 → 0.2.14

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.
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ import { mkdtemp, rm, readFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { spawn } from "node:child_process";
6
+
7
+ const args = process.argv.slice(2);
8
+
9
+ if (args.includes("--help") || args.includes("-h")) {
10
+ printHelp();
11
+ process.exit(0);
12
+ }
13
+
14
+ const dryRun = takeFlag("--dry-run") || takeFlag("-n") || isTruthy(process.env.npm_config_dry_run);
15
+ const skipInstall = takeFlag("--no-install");
16
+ const skipPublish = takeFlag("--no-publish");
17
+ const registry = takeOption("--registry") ?? "https://registry.npmjs.org/";
18
+ const tag = takeOption("--tag") ?? "latest";
19
+ const positional = args.filter((arg) => !arg.startsWith("-"));
20
+ const bump = positional[0] ?? "patch";
21
+
22
+ const root = process.cwd();
23
+ const packageJsonPath = path.join(root, "package.json");
24
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
25
+ const packageName = packageJson.name;
26
+ const currentVersion = packageJson.version;
27
+ const targetVersion = resolveTargetVersion(currentVersion, bump);
28
+
29
+ console.log(`[release-local] package: ${packageName}`);
30
+ console.log(`[release-local] current: ${currentVersion}`);
31
+ console.log(`[release-local] target: ${targetVersion}`);
32
+ console.log(`[release-local] registry: ${registry}`);
33
+ console.log(`[release-local] tag: ${tag}`);
34
+
35
+ if (dryRun) {
36
+ console.log("[release-local] dry run: version files will not be modified, package will not be published, global install will not run");
37
+ await run("npm", ["pack", "--dry-run"], { cwd: root });
38
+ console.log(`[release-local] dry run complete. Would publish ${packageName}@${targetVersion} and install it globally.`);
39
+ process.exit(0);
40
+ }
41
+
42
+ await run("npm", ["version", targetVersion, "--no-git-tag-version"], { cwd: root });
43
+
44
+ let userconfig;
45
+ try {
46
+ const token = process.env.NODE_AUTH_TOKEN || process.env.NPM_TOKEN;
47
+ if (token) {
48
+ userconfig = await writeTempNpmrc(registry, token);
49
+ }
50
+
51
+ if (!skipPublish) {
52
+ const publishArgs = ["publish", "--registry", registry, "--tag", tag];
53
+ if (userconfig) publishArgs.push("--userconfig", userconfig);
54
+ await run("npm", publishArgs, { cwd: root });
55
+ } else {
56
+ console.log("[release-local] --no-publish: skipping npm publish");
57
+ }
58
+
59
+ if (!skipPublish) {
60
+ await waitForPublishedVersion(packageName, targetVersion, registry, root);
61
+ }
62
+
63
+ if (!skipInstall) {
64
+ await run("npm", ["install", "-g", `${packageName}@${targetVersion}`, "--registry", registry], { cwd: root });
65
+ } else {
66
+ console.log("[release-local] --no-install: skipping global install");
67
+ }
68
+
69
+ if (!skipPublish) {
70
+ await run("npm", ["view", packageName, "version", "dist-tags", "--registry", registry], { cwd: root });
71
+ }
72
+ if (!skipInstall) {
73
+ await run("npm", ["list", "-g", packageName, "--depth=0"], { cwd: root });
74
+ }
75
+
76
+ console.log(`[release-local] done: ${packageName}@${targetVersion}`);
77
+ } finally {
78
+ if (userconfig) await rm(userconfig, { force: true }).catch(() => undefined);
79
+ }
80
+
81
+ function takeFlag(name) {
82
+ const index = args.indexOf(name);
83
+ if (index === -1) return false;
84
+ args.splice(index, 1);
85
+ return true;
86
+ }
87
+
88
+ function takeOption(name) {
89
+ const equalsIndex = args.findIndex((arg) => arg.startsWith(`${name}=`));
90
+ if (equalsIndex !== -1) {
91
+ const value = args[equalsIndex].slice(name.length + 1);
92
+ args.splice(equalsIndex, 1);
93
+ return value;
94
+ }
95
+ const index = args.indexOf(name);
96
+ if (index === -1) return undefined;
97
+ const value = args[index + 1];
98
+ if (!value || value.startsWith("-")) throw new Error(`Missing value for ${name}`);
99
+ args.splice(index, 2);
100
+ return value;
101
+ }
102
+
103
+ function isTruthy(value) {
104
+ return value === "true" || value === "1" || value === "yes";
105
+ }
106
+
107
+ function resolveTargetVersion(currentVersion, bump) {
108
+ if (/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(bump)) return bump;
109
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(currentVersion);
110
+ if (!match) throw new Error(`Cannot bump non-standard version: ${currentVersion}`);
111
+ const major = Number(match[1]);
112
+ const minor = Number(match[2]);
113
+ const patch = Number(match[3]);
114
+ if (bump === "patch") return `${major}.${minor}.${patch + 1}`;
115
+ if (bump === "minor") return `${major}.${minor + 1}.0`;
116
+ if (bump === "major") return `${major + 1}.0.0`;
117
+ throw new Error(`Unknown bump/version '${bump}'. Use patch, minor, major, or an explicit x.y.z version.`);
118
+ }
119
+
120
+ async function writeTempNpmrc(registry, token) {
121
+ const dir = await mkdtemp(path.join(tmpdir(), "neoctl-release-"));
122
+ const file = path.join(dir, ".npmrc");
123
+ const registryUrl = new URL(registry);
124
+ const fs = await import("node:fs/promises");
125
+ await fs.writeFile(file, `//${registryUrl.host}/:_authToken=${token}\nregistry=${registry}\n`, "utf8");
126
+ return file;
127
+ }
128
+
129
+ async function waitForPublishedVersion(packageName, targetVersion, registry, cwd) {
130
+ const deadline = Date.now() + 180_000;
131
+ console.log(`[release-local] waiting for ${packageName}@${targetVersion} to be visible on ${registry}`);
132
+ while (Date.now() < deadline) {
133
+ const result = await runCapture("npm", ["view", packageName, "version", "--registry", registry], { cwd });
134
+ const version = result.stdout.trim();
135
+ if (result.code === 0 && version === targetVersion) {
136
+ console.log(`[release-local] registry version visible: ${version}`);
137
+ return;
138
+ }
139
+ console.log(`[release-local] registry version is ${version || "unavailable"}; retrying...`);
140
+ await delay(5_000);
141
+ }
142
+ throw new Error(`Timed out waiting for ${packageName}@${targetVersion} to be visible on ${registry}`);
143
+ }
144
+
145
+ function delay(ms) {
146
+ return new Promise((resolve) => setTimeout(resolve, ms));
147
+ }
148
+
149
+ function run(command, commandArgs, options = {}) {
150
+ console.log(`[release-local] $ ${command} ${commandArgs.map(quoteArg).join(" ")}`);
151
+ return new Promise((resolve, reject) => {
152
+ const child = spawn(command, commandArgs, {
153
+ cwd: options.cwd,
154
+ shell: process.platform === "win32",
155
+ stdio: "inherit",
156
+ env: process.env,
157
+ });
158
+ child.on("error", reject);
159
+ child.on("exit", (code, signal) => {
160
+ if (code === 0) resolve();
161
+ else reject(new Error(`${command} exited with ${signal ?? code}`));
162
+ });
163
+ });
164
+ }
165
+
166
+ function runCapture(command, commandArgs, options = {}) {
167
+ console.log(`[release-local] $ ${command} ${commandArgs.map(quoteArg).join(" ")}`);
168
+ return new Promise((resolve, reject) => {
169
+ const child = spawn(command, commandArgs, {
170
+ cwd: options.cwd,
171
+ shell: process.platform === "win32",
172
+ stdio: ["ignore", "pipe", "pipe"],
173
+ env: process.env,
174
+ });
175
+ let stdout = "";
176
+ let stderr = "";
177
+ child.stdout.on("data", (chunk) => { stdout += String(chunk); });
178
+ child.stderr.on("data", (chunk) => { stderr += String(chunk); });
179
+ child.on("error", reject);
180
+ child.on("exit", (code, signal) => resolve({ code: code ?? 1, signal, stdout, stderr }));
181
+ });
182
+ }
183
+
184
+ function quoteArg(arg) {
185
+ return /\s/.test(arg) ? JSON.stringify(arg) : arg;
186
+ }
187
+
188
+ function printHelp() {
189
+ console.log(`Usage: npm run release:local -- [patch|minor|major|x.y.z] [options]\n\nPublishes a new npm version and updates the local global install to that exact version.\n\nDefault bump is patch. The script updates package.json/package-lock.json using\n'npm version <version> --no-git-tag-version', runs npm publish, then runs\n'npm install -g <name>@<version>'.\n\nOptions:\n --dry-run, -n Run npm pack --dry-run only; do not modify, publish, or install\n --registry <url> Registry to publish/install from (default: https://registry.npmjs.org/)\n --tag <tag> npm dist-tag for publish (default: latest)\n --no-publish Bump version and install, but skip npm publish\n --no-install Bump version and publish, but skip global install\n -h, --help Show this help\n\nAuth:\n Uses NODE_AUTH_TOKEN or NPM_TOKEN when set; otherwise relies on npm login.\n\nExamples:\n npm run release:local\n npm run release:local -- minor\n npm run release:local -- 0.3.0\n NODE_AUTH_TOKEN=... npm run release:local -- patch\n`);
190
+ }