@lpm-registry/cli 0.46.0 → 0.47.0
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/bin/lpm.js +250 -241
- package/lib/commands/npmrc.js +169 -169
- package/lib/commands/setup.js +192 -188
- package/package.json +6 -6
package/lib/commands/npmrc.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import fs from "node:fs"
|
|
2
|
-
import path from "node:path"
|
|
3
|
-
import { request } from "../api.js"
|
|
4
|
-
import { getRegistryUrl, getToken } from "../config.js"
|
|
5
|
-
import { createSpinner, log, printHeader } from "../ui.js"
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { request } from "../api.js";
|
|
4
|
+
import { getRegistryUrl, getToken } from "../config.js";
|
|
5
|
+
import { createSpinner, log, printHeader } from "../ui.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Generate a read-only .npmrc token for local development.
|
|
@@ -10,169 +10,169 @@ import { createSpinner, log, printHeader } from "../ui.js"
|
|
|
10
10
|
* Also ensures .npmrc is in .gitignore to prevent accidental commits.
|
|
11
11
|
*/
|
|
12
12
|
export async function npmrc(options) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
13
|
+
printHeader();
|
|
14
|
+
|
|
15
|
+
const token = await getToken();
|
|
16
|
+
if (!token) {
|
|
17
|
+
log.error(
|
|
18
|
+
'You must be logged in to generate an .npmrc token. Run "lpm login" first.',
|
|
19
|
+
);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const projectRoot = process.cwd();
|
|
24
|
+
const npmrcPath = path.join(projectRoot, ".npmrc");
|
|
25
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
26
|
+
|
|
27
|
+
// Check for package.json
|
|
28
|
+
const pkgJsonPath = path.join(projectRoot, "package.json");
|
|
29
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
30
|
+
log.warn("No package.json found. Run this command in your project root.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Derive project name for the token label
|
|
34
|
+
let projectName = "project";
|
|
35
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
36
|
+
try {
|
|
37
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
|
|
38
|
+
if (pkg.name) {
|
|
39
|
+
projectName = pkg.name.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Invalid package.json — use default name
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const expiryDays = parseInt(options?.days, 10) || 30;
|
|
47
|
+
const spinner = createSpinner("Creating read-only token...").start();
|
|
48
|
+
|
|
49
|
+
// Call server to create a scoped read-only token
|
|
50
|
+
let readToken;
|
|
51
|
+
let expiresAt;
|
|
52
|
+
try {
|
|
53
|
+
const tokenName = `npmrc-${projectName}-${new Date().toISOString().slice(0, 10)}`;
|
|
54
|
+
const response = await request("/-/token/create", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
scope: "read",
|
|
58
|
+
name: tokenName,
|
|
59
|
+
expiryDays,
|
|
60
|
+
}),
|
|
61
|
+
headers: { "Content-Type": "application/json" },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const text = await response.text();
|
|
66
|
+
throw new Error(`Token creation failed: ${response.status} ${text}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
readToken = data.token;
|
|
71
|
+
expiresAt = data.expiresAt;
|
|
72
|
+
spinner.succeed("Read-only token created.");
|
|
73
|
+
} catch (error) {
|
|
74
|
+
spinner.fail(`Failed to create token: ${error.message}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Build .npmrc content
|
|
79
|
+
const registryUrl = getRegistryUrl();
|
|
80
|
+
const fullRegistryUrl = registryUrl.endsWith("/api/registry")
|
|
81
|
+
? registryUrl
|
|
82
|
+
: `${registryUrl}/api/registry`;
|
|
83
|
+
const registryHost = fullRegistryUrl.replace(/^https?:/, "");
|
|
84
|
+
|
|
85
|
+
// Default: scoped (only @lpm.dev packages through lpm.dev).
|
|
86
|
+
// --proxy: route all npm traffic through lpm.dev (Pro/Org feature).
|
|
87
|
+
let useScoped = !options?.proxy;
|
|
88
|
+
|
|
89
|
+
// Read existing .npmrc and check for custom default registry
|
|
90
|
+
let npmrcContent = "";
|
|
91
|
+
if (fs.existsSync(npmrcPath)) {
|
|
92
|
+
const existingContent = fs.readFileSync(npmrcPath, "utf8");
|
|
93
|
+
|
|
94
|
+
// Detect custom default registry (not npmjs.org and not lpm.dev)
|
|
95
|
+
if (!useScoped) {
|
|
96
|
+
const registryLine = existingContent
|
|
97
|
+
.split("\n")
|
|
98
|
+
.find(
|
|
99
|
+
(line) =>
|
|
100
|
+
line.match(/^registry=/) &&
|
|
101
|
+
!line.includes("registry.npmjs.org") &&
|
|
102
|
+
!line.includes("lpm.dev"),
|
|
103
|
+
);
|
|
104
|
+
if (registryLine) {
|
|
105
|
+
log.warn(`Found existing default registry: ${registryLine.trim()}`);
|
|
106
|
+
log.warn("Using --scoped mode to avoid overriding it.");
|
|
107
|
+
log.warn(
|
|
108
|
+
"To use LPM as your default registry, remove the existing registry= line first.",
|
|
109
|
+
);
|
|
110
|
+
useScoped = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Remove old LPM config lines (both scoped and unscoped formats)
|
|
115
|
+
npmrcContent = existingContent
|
|
116
|
+
.split("\n")
|
|
117
|
+
.filter((line) => {
|
|
118
|
+
return (
|
|
119
|
+
!line.includes("@lpm.dev:registry") &&
|
|
120
|
+
!line.match(/^registry=.*lpm\.dev/) &&
|
|
121
|
+
!line.includes("lpm.dev/api/registry/:_authToken") &&
|
|
122
|
+
!line.includes("_authToken=lpm_") &&
|
|
123
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: matching literal npm token placeholder
|
|
124
|
+
!line.includes("_authToken=${LPM_TOKEN}") &&
|
|
125
|
+
!line.includes("# LPM Registry")
|
|
126
|
+
);
|
|
127
|
+
})
|
|
128
|
+
.join("\n")
|
|
129
|
+
.trim();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const registryLine = useScoped
|
|
133
|
+
? `@lpm.dev:registry=${fullRegistryUrl}`
|
|
134
|
+
: `registry=${fullRegistryUrl}`;
|
|
135
|
+
|
|
136
|
+
const lpmConfig = `# LPM Registry (generated by lpm setup local — do not commit)
|
|
137
137
|
${registryLine}
|
|
138
|
-
${registryHost}/:_authToken=${readToken}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
138
|
+
${registryHost}/:_authToken=${readToken}`;
|
|
139
|
+
|
|
140
|
+
npmrcContent = npmrcContent ? `${npmrcContent}\n\n${lpmConfig}` : lpmConfig;
|
|
141
|
+
fs.writeFileSync(npmrcPath, `${npmrcContent}\n`);
|
|
142
|
+
|
|
143
|
+
// Ensure .npmrc is in .gitignore
|
|
144
|
+
let gitignoreUpdated = false;
|
|
145
|
+
if (fs.existsSync(gitignorePath)) {
|
|
146
|
+
const gitignore = fs.readFileSync(gitignorePath, "utf8");
|
|
147
|
+
if (!gitignore.split("\n").some((line) => line.trim() === ".npmrc")) {
|
|
148
|
+
fs.writeFileSync(gitignorePath, `${gitignore.trimEnd()}\n.npmrc\n`);
|
|
149
|
+
gitignoreUpdated = true;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
fs.writeFileSync(gitignorePath, ".npmrc\n");
|
|
153
|
+
gitignoreUpdated = true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Output
|
|
157
|
+
console.log("");
|
|
158
|
+
log.success(".npmrc configured with read-only LPM token.");
|
|
159
|
+
if (gitignoreUpdated) {
|
|
160
|
+
log.info(".npmrc added to .gitignore to prevent token leaks.");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (useScoped) {
|
|
164
|
+
log.info("Only @lpm.dev packages will route through lpm.dev.");
|
|
165
|
+
} else {
|
|
166
|
+
log.info("All packages (LPM + npm) will route through lpm.dev.");
|
|
167
|
+
log.info("Note: First install may update resolved URLs in your lockfile.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const expiryDate = new Date(expiresAt).toLocaleDateString("en-US", {
|
|
171
|
+
year: "numeric",
|
|
172
|
+
month: "long",
|
|
173
|
+
day: "numeric",
|
|
174
|
+
});
|
|
175
|
+
log.info(`Token expires: ${expiryDate}`);
|
|
176
|
+
log.info("Run `lpm setup local` again to refresh when expired.");
|
|
177
|
+
console.log("");
|
|
178
178
|
}
|