@lpm-registry/cli 0.58.0 → 0.59.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.
Files changed (62) hide show
  1. package/README.md +25 -44
  2. package/bin/lpm.js +2 -354
  3. package/bin/lpx.js +4 -0
  4. package/bin/native.js +154 -0
  5. package/package.json +42 -82
  6. package/scripts/install-binary.js +219 -550
  7. package/CHANGELOG.md +0 -197
  8. package/LICENSE +0 -15
  9. package/index.d.ts +0 -131
  10. package/index.js +0 -31
  11. package/lib/api.js +0 -398
  12. package/lib/commands/add.js +0 -1217
  13. package/lib/commands/audit.js +0 -283
  14. package/lib/commands/cache.js +0 -209
  15. package/lib/commands/check-name.js +0 -112
  16. package/lib/commands/config.js +0 -186
  17. package/lib/commands/doctor.js +0 -140
  18. package/lib/commands/info.js +0 -215
  19. package/lib/commands/init.js +0 -146
  20. package/lib/commands/install.js +0 -264
  21. package/lib/commands/login.js +0 -595
  22. package/lib/commands/logout.js +0 -94
  23. package/lib/commands/marketplace-compare.js +0 -164
  24. package/lib/commands/marketplace-earnings.js +0 -89
  25. package/lib/commands/mcp-setup.js +0 -363
  26. package/lib/commands/npmrc.js +0 -178
  27. package/lib/commands/open.js +0 -82
  28. package/lib/commands/outdated.js +0 -291
  29. package/lib/commands/pool-stats.js +0 -100
  30. package/lib/commands/publish.js +0 -949
  31. package/lib/commands/quality.js +0 -211
  32. package/lib/commands/remove.js +0 -82
  33. package/lib/commands/run.js +0 -14
  34. package/lib/commands/search.js +0 -143
  35. package/lib/commands/setup.js +0 -221
  36. package/lib/commands/skills.js +0 -898
  37. package/lib/commands/token-rotate.js +0 -25
  38. package/lib/commands/uninstall.js +0 -107
  39. package/lib/commands/whoami.js +0 -129
  40. package/lib/config.js +0 -328
  41. package/lib/constants.js +0 -190
  42. package/lib/ecosystem.js +0 -501
  43. package/lib/editor-skills.js +0 -321
  44. package/lib/editors.js +0 -215
  45. package/lib/import-rewriter.js +0 -364
  46. package/lib/install-targets/mcp-server.js +0 -245
  47. package/lib/install-targets/vscode-extension.js +0 -178
  48. package/lib/install-targets.js +0 -82
  49. package/lib/integrity.js +0 -179
  50. package/lib/lpm-config-prompts.js +0 -102
  51. package/lib/lpm-config.js +0 -408
  52. package/lib/oidc.js +0 -166
  53. package/lib/project-utils.js +0 -152
  54. package/lib/quality/checks.js +0 -687
  55. package/lib/quality/display.js +0 -155
  56. package/lib/quality/score.js +0 -118
  57. package/lib/quality/swift-checks.js +0 -447
  58. package/lib/safe-path.js +0 -180
  59. package/lib/secure-store.js +0 -321
  60. package/lib/swift-project.js +0 -637
  61. package/lib/ui.js +0 -40
  62. package/scripts/__tests__/install-binary.test.js +0 -346
@@ -1,211 +0,0 @@
1
- import chalk from "chalk"
2
- import ora from "ora"
3
- import { get } from "../api.js"
4
-
5
- const TIER_COLORS = {
6
- excellent: chalk.green,
7
- good: chalk.blue,
8
- fair: chalk.yellow,
9
- "needs-work": chalk.gray,
10
- }
11
-
12
- const TIER_LABELS = {
13
- excellent: "Excellent",
14
- good: "Good",
15
- fair: "Fair",
16
- "needs-work": "Needs Work",
17
- }
18
-
19
- const CATEGORY_LABELS = {
20
- documentation: "Documentation",
21
- code: "Code Quality",
22
- testing: "Testing",
23
- health: "Package Health",
24
- }
25
-
26
- function progressBar(value, max, width = 18) {
27
- const ratio = Math.min(value / max, 1)
28
- const filled = Math.round(ratio * width)
29
- const empty = width - filled
30
- return chalk.cyan("█".repeat(filled)) + chalk.gray("░".repeat(empty))
31
- }
32
-
33
- /**
34
- * Parse package name input.
35
- * Accepts: "owner.pkg", "@lpm.dev/owner.pkg"
36
- * @param {string} input
37
- * @returns {string|null} cleaned name as "owner.pkg" or null if invalid
38
- */
39
- function parseName(input) {
40
- let cleaned = input
41
- if (cleaned.startsWith("@lpm.dev/")) {
42
- cleaned = cleaned.replace("@lpm.dev/", "")
43
- }
44
- const dotIndex = cleaned.indexOf(".")
45
- if (dotIndex === -1 || dotIndex === 0 || dotIndex === cleaned.length - 1) {
46
- return null
47
- }
48
- return cleaned
49
- }
50
-
51
- const TIPS = {
52
- "has-readme": "Add a README.md with at least 100 characters",
53
- "readme-install": "Add an install/getting started section to your README",
54
- "readme-usage": "Add usage examples with code blocks to your README",
55
- "readme-api": "Add an API/reference section to your README",
56
- "has-changelog": "Add a CHANGELOG.md file",
57
- "has-license": "Add a LICENSE file",
58
- "has-types": 'Add TypeScript types ("types" field or .d.ts files)',
59
- "intellisense-coverage": "Add .d.ts type definitions or JSDoc comments",
60
- "esm-exports": 'Add "type": "module" or "exports" to package.json',
61
- "tree-shakable": 'Add "sideEffects": false to package.json',
62
- "no-eval": "Remove eval() and new Function() usage",
63
- "has-engines": 'Add "engines": { "node": ">=18" } to package.json',
64
- "has-exports-map": 'Add an "exports" map to package.json',
65
- "small-deps": "Reduce the number of production dependencies",
66
- "source-maps": "Include .js.map source maps for easier debugging",
67
- "has-test-files": "Add test files (*.test.js, *.spec.js)",
68
- "has-test-script": "Add a test script to package.json",
69
- "has-description": "Add a description (>10 chars) to package.json",
70
- "has-keywords": "Add keywords to package.json",
71
- "has-repository": "Add a repository field to package.json",
72
- "has-homepage": "Add a homepage field to package.json",
73
- "reasonable-size": "Reduce unpacked size (check for unnecessary files)",
74
- "no-vulnerabilities": "Fix known vulnerabilities in dependencies",
75
- "maintenance-health": "Publish updates regularly (within 90 days)",
76
- "semver-consistency": "Use valid semantic versioning (major.minor.patch)",
77
- "author-verified": "Link your GitHub or LinkedIn in your profile settings",
78
- }
79
-
80
- /**
81
- * Fetch and display the server-side quality report for a package.
82
- *
83
- * @param {string} nameInput - Package name (e.g., "owner.pkg" or "@lpm.dev/owner.pkg")
84
- * @param {Object} [options]
85
- * @param {boolean} [options.json] - Output as JSON
86
- */
87
- export async function quality(nameInput, options = {}) {
88
- if (!nameInput || nameInput.trim() === "") {
89
- if (options.json) {
90
- console.log(JSON.stringify({ error: "Package name required." }))
91
- } else {
92
- console.error(chalk.red("Error: Package name required."))
93
- console.log(chalk.dim("Usage: lpm quality owner.package-name"))
94
- }
95
- process.exit(1)
96
- }
97
-
98
- const cleaned = parseName(nameInput.trim())
99
- if (!cleaned) {
100
- if (options.json) {
101
- console.log(
102
- JSON.stringify({
103
- error: "Invalid package name format. Use: owner.package-name",
104
- }),
105
- )
106
- } else {
107
- console.error(chalk.red("Error: Invalid package name format."))
108
- console.log(chalk.dim("Expected: owner.package-name"))
109
- }
110
- process.exit(1)
111
- }
112
-
113
- const fullName = `@lpm.dev/${cleaned}`
114
- const spinner = options.json
115
- ? null
116
- : ora(`Fetching quality report for ${fullName}...`).start()
117
-
118
- try {
119
- const response = await get(`/quality?name=${encodeURIComponent(cleaned)}`, {
120
- onRetry: spinner
121
- ? (attempt, max) => {
122
- spinner.text = `Fetching (retry ${attempt}/${max})...`
123
- }
124
- : undefined,
125
- })
126
-
127
- const data = await response.json().catch(() => ({}))
128
-
129
- if (!response.ok) {
130
- throw new Error(data.error || `Request failed: ${response.status}`)
131
- }
132
-
133
- if (options.json) {
134
- console.log(JSON.stringify(data, null, 2))
135
- return
136
- }
137
-
138
- if (spinner) spinner.stop()
139
-
140
- // No quality data
141
- if (data.score === null || data.score === undefined) {
142
- console.log("")
143
- console.log(chalk.yellow(` ${fullName}: No quality data available.`))
144
- console.log(
145
- chalk.dim(" Publish with lpm CLI v0.16+ to generate quality scores."),
146
- )
147
- console.log("")
148
- return
149
- }
150
-
151
- // Display quality report
152
- const tier = data.tier || "needs-work"
153
- const tierColor = TIER_COLORS[tier] || chalk.white
154
- const tierLabel = TIER_LABELS[tier] || tier
155
-
156
- console.log("")
157
- console.log(` ${chalk.bold(fullName)}`)
158
- console.log(
159
- ` ${chalk.bold("Quality Score:")} ${tierColor(`${data.score}/${data.maxScore}`)} ${tierColor(`(${tierLabel})`)}`,
160
- )
161
- console.log("")
162
-
163
- // Category bars
164
- if (data.categories) {
165
- for (const [cat, { score: catScore, max }] of Object.entries(
166
- data.categories,
167
- )) {
168
- const label = (CATEGORY_LABELS[cat] || cat).padEnd(16)
169
- const bar = progressBar(catScore, max)
170
- console.log(` ${label}${bar} ${catScore}/${max}`)
171
- }
172
- console.log("")
173
- }
174
-
175
- // Individual checks
176
- if (data.checks && data.checks.length > 0) {
177
- for (const check of data.checks) {
178
- const icon = check.passed ? chalk.green("✓") : chalk.red("✗")
179
- const label = check.passed ? check.label : chalk.dim(check.label)
180
- const points = check.passed
181
- ? chalk.dim(` ${check.points}/${check.max_points}`)
182
- : chalk.dim(` 0/${check.max_points}`)
183
- const detail = check.detail ? chalk.dim(` — ${check.detail}`) : ""
184
- console.log(` ${icon} ${label}${points}${detail}`)
185
- }
186
-
187
- // Tips for failed checks
188
- const failed = data.checks.filter(c => !c.passed)
189
- if (failed.length > 0) {
190
- console.log("")
191
- const tips = failed.slice(0, 3)
192
- for (const check of tips) {
193
- const tip = TIPS[check.id] || `Improve: ${check.id}`
194
- console.log(chalk.dim(` Tip: ${tip} (+${check.max_points} points)`))
195
- }
196
- }
197
- }
198
-
199
- console.log("")
200
- } catch (error) {
201
- if (spinner) spinner.fail(chalk.red("Failed to fetch quality report."))
202
- if (options.json) {
203
- console.log(JSON.stringify({ error: error.message }))
204
- } else {
205
- console.error(chalk.red(` ${error.message}`))
206
- }
207
- process.exit(1)
208
- }
209
- }
210
-
211
- export default quality
@@ -1,82 +0,0 @@
1
- import * as p from "@clack/prompts"
2
- import chalk from "chalk"
3
- import { getRegistryUrl, getToken } from "../config.js"
4
- import { getHandler, hasCustomHandler } from "../install-targets.js"
5
-
6
- /**
7
- * Remove a previously added package.
8
- *
9
- * For packages with custom install handlers (e.g., MCP servers), this
10
- * delegates to the handler's remove function. For standard source packages,
11
- * it advises manual removal since files were copied into the project.
12
- *
13
- * @param {string} pkgName - Package name (e.g., "@lpm.dev/owner.my-pkg")
14
- * @param {object} options - CLI options
15
- */
16
- export async function remove(pkgName, _options) {
17
- p.intro(chalk.bgCyan(chalk.black(" lpm remove ")))
18
-
19
- // 1. Auth Check
20
- const token = await getToken()
21
- if (!token) {
22
- p.log.error("Not logged in. Run `lpm login` first.")
23
- p.outro("")
24
- return
25
- }
26
-
27
- // 2. Normalize package name
28
- const name = pkgName.startsWith("@lpm.dev/") ? pkgName : `@lpm.dev/${pkgName}`
29
-
30
- // 3. Fetch package metadata to determine type
31
- const baseRegistryUrl = getRegistryUrl()
32
- const registryUrl = baseRegistryUrl.endsWith("/api/registry")
33
- ? baseRegistryUrl
34
- : `${baseRegistryUrl}/api/registry`
35
- const encodedName = name.replace("/", "%2f")
36
-
37
- let meta
38
- try {
39
- const res = await fetch(`${registryUrl}/${encodedName}`, {
40
- headers: { Authorization: `Bearer ${token}` },
41
- })
42
-
43
- if (!res.ok) {
44
- if (res.status === 404) {
45
- p.log.error(`Package '${name}' not found.`)
46
- p.outro("")
47
- return
48
- }
49
- throw new Error(res.statusText)
50
- }
51
-
52
- meta = await res.json()
53
- } catch (err) {
54
- p.log.error(`Failed to fetch package info: ${err.message}`)
55
- p.outro("")
56
- return
57
- }
58
-
59
- // 4. Determine package type from latest version's lpmConfig
60
- const latestVersion = meta["dist-tags"]?.latest
61
- const versionData = latestVersion ? meta.versions[latestVersion] : null
62
- const packageType = versionData?.lpmConfig?.type || meta.packageType
63
-
64
- // 5. Route to type-specific handler or show manual instructions
65
- if (packageType && hasCustomHandler(packageType)) {
66
- const handler = getHandler(packageType)
67
- const result = await handler.remove({ name })
68
-
69
- if (result.success) {
70
- p.log.success(result.message)
71
- } else {
72
- p.log.error(result.message)
73
- }
74
- } else {
75
- p.log.info(
76
- `${chalk.cyan(name)} was installed as source files copied into your project.`,
77
- )
78
- p.log.info("Remove the files manually from your project directory.")
79
- }
80
-
81
- p.outro("")
82
- }
@@ -1,14 +0,0 @@
1
- import { spawn } from "node:child_process"
2
- import { log } from "../ui.js"
3
-
4
- export async function run(script, _options, command) {
5
- const args = ["run", script, ...command.args]
6
-
7
- const child = spawn("npm", args, { stdio: "inherit" })
8
-
9
- child.on("close", code => process.exit(code))
10
- child.on("error", err => {
11
- log.error(`Failed to start npm: ${err.message}`)
12
- process.exit(1)
13
- })
14
- }
@@ -1,143 +0,0 @@
1
- /**
2
- * Search Command
3
- *
4
- * Search for packages using hybrid keyword + semantic search.
5
- *
6
- * Usage:
7
- * lpm search <query> [options]
8
- *
9
- * Options:
10
- * --limit <n> Maximum number of results (default: 20)
11
- * --json Output in JSON format
12
- *
13
- * @module cli/lib/commands/search
14
- */
15
-
16
- import chalk from "chalk"
17
- import ora from "ora"
18
- import { searchGet } from "../api.js"
19
- import { DEFAULT_PAGE_LIMIT } from "../constants.js"
20
-
21
- /**
22
- * Format downloads count.
23
- * @param {number} count
24
- * @returns {string}
25
- */
26
- function formatDownloads(count) {
27
- if (count >= 1000000) {
28
- return `${(count / 1000000).toFixed(1)}M`
29
- }
30
- if (count >= 1000) {
31
- return `${(count / 1000).toFixed(1)}K`
32
- }
33
- return String(count)
34
- }
35
-
36
- /**
37
- * Execute the search command.
38
- *
39
- * @param {string} query - Search query
40
- * @param {Object} options - Command options
41
- * @param {number} [options.limit] - Maximum results
42
- * @param {boolean} [options.json] - Output as JSON
43
- */
44
- export async function search(query, options = {}) {
45
- if (!query || query.trim() === "") {
46
- console.error(chalk.red("Error: Search query required."))
47
- console.log(chalk.dim("Usage: lpm search <query>"))
48
- process.exit(1)
49
- }
50
-
51
- const limit = options.limit || DEFAULT_PAGE_LIMIT
52
- const spinner = ora(`Searching for "${query}"...`).start()
53
-
54
- try {
55
- const params = new URLSearchParams({
56
- q: query,
57
- mode: "semantic",
58
- limit: String(limit),
59
- })
60
-
61
- const response = await searchGet(`/packages?${params.toString()}`, {
62
- onRetry: (attempt, max) => {
63
- spinner.text = `Searching (retry ${attempt}/${max})...`
64
- },
65
- })
66
-
67
- if (!response.ok) {
68
- const data = await response.json().catch(() => ({}))
69
- throw new Error(
70
- data.error || `Search failed with status ${response.status}`,
71
- )
72
- }
73
-
74
- const data = await response.json()
75
- const packages = data.packages || []
76
-
77
- spinner.stop()
78
-
79
- if (options.json) {
80
- console.log(JSON.stringify(packages, null, 2))
81
- return
82
- }
83
-
84
- if (packages.length === 0) {
85
- console.log(chalk.yellow(`\nNo packages found for "${query}".`))
86
- return
87
- }
88
-
89
- console.log(
90
- chalk.bold(
91
- `\nFound ${packages.length} package${packages.length > 1 ? "s" : ""}:\n`,
92
- ),
93
- )
94
-
95
- // Calculate column widths (use LPM format: @lpm.dev/owner.name)
96
- const maxNameLength = Math.max(
97
- ...packages.map(p => {
98
- const owner = p.ownerSlug || p.owner
99
- return (owner ? `@lpm.dev/${owner}.${p.name}` : p.name).length
100
- }),
101
- 10,
102
- )
103
-
104
- for (const pkg of packages) {
105
- const owner = pkg.ownerSlug || pkg.owner
106
- const fullName = owner ? `@lpm.dev/${owner}.${pkg.name}` : pkg.name
107
- const paddedName = fullName.padEnd(maxNameLength)
108
- const version = chalk.dim(
109
- `v${pkg.latestVersion || pkg.version || "unknown"}`.padEnd(12),
110
- )
111
- const downloads = chalk.green(
112
- `↓ ${formatDownloads(pkg.downloadCount ?? pkg.downloads ?? 0)}`.padEnd(
113
- 10,
114
- ),
115
- )
116
-
117
- console.log(` ${chalk.cyan(paddedName)} ${version} ${downloads}`)
118
-
119
- if (pkg.description) {
120
- const truncatedDesc =
121
- pkg.description.length > 60
122
- ? `${pkg.description.slice(0, 57)}...`
123
- : pkg.description
124
- console.log(` ${chalk.dim(truncatedDesc)}`)
125
- }
126
-
127
- console.log("")
128
- }
129
-
130
- if (packages.length === limit) {
131
- console.log(
132
- chalk.dim(` Showing first ${limit} results. Use --limit to see more.`),
133
- )
134
- console.log("")
135
- }
136
- } catch (error) {
137
- spinner.fail(chalk.red("Search failed."))
138
- console.error(chalk.red(` ${error.message}`))
139
- process.exit(1)
140
- }
141
- }
142
-
143
- export default search
@@ -1,221 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import * as p from "@clack/prompts";
4
- import chalk from "chalk";
5
- import { getRegistryUrl } from "../config.js";
6
- import { exchangeOidcInstallToken } from "../oidc.js";
7
- import { log, printHeader } from "../ui.js";
8
-
9
- /**
10
- * Remove existing LPM config lines from .npmrc content.
11
- * Handles both scoped (@lpm.dev:registry=) and unscoped (registry=...lpm.dev) formats.
12
- * @param {string} content
13
- * @returns {string}
14
- */
15
- function removeLpmLines(content) {
16
- return content
17
- .split("\n")
18
- .filter((line) => {
19
- return (
20
- !line.includes("@lpm.dev:registry") &&
21
- !line.match(/^registry=.*lpm\.dev/) &&
22
- !line.includes("lpm.dev/api/registry/:_authToken") &&
23
- !line.includes("_authToken=lpm_") &&
24
- // biome-ignore lint/suspicious/noTemplateCurlyInString: matching literal npm token placeholder
25
- !line.includes("_authToken=${LPM_TOKEN}") &&
26
- !line.includes("# LPM Registry")
27
- );
28
- })
29
- .join("\n")
30
- .trim();
31
- }
32
-
33
- /**
34
- * Check if .npmrc has a custom default registry that is NOT npmjs.org or lpm.dev.
35
- * @param {string} content - Existing .npmrc content
36
- * @returns {string|null} The custom registry line, or null if none found
37
- */
38
- function detectCustomRegistry(content) {
39
- return (
40
- content
41
- .split("\n")
42
- .find(
43
- (line) =>
44
- line.match(/^registry=/) &&
45
- !line.includes("registry.npmjs.org") &&
46
- !line.includes("lpm.dev"),
47
- ) || null
48
- );
49
- }
50
-
51
- /**
52
- * Configure CI/CD .npmrc for LPM packages.
53
- *
54
- * Default mode: writes ${LPM_TOKEN} placeholder (for CI with env var).
55
- * --oidc mode: exchanges CI OIDC token for a real short-lived token (no secrets needed).
56
- */
57
- export async function setup(options) {
58
- printHeader();
59
-
60
- p.intro(chalk.bgCyan(chalk.black(" lpm setup ci ")));
61
-
62
- const registryUrl = options?.registry || getRegistryUrl();
63
- const projectRoot = process.cwd();
64
- const npmrcPath = path.join(projectRoot, ".npmrc");
65
-
66
- // Check for package.json
67
- const pkgJsonPath = path.join(projectRoot, "package.json");
68
- if (!fs.existsSync(pkgJsonPath)) {
69
- log.warn("No package.json found. Run this command in your project root.");
70
- }
71
-
72
- // Build registry URL
73
- const fullRegistryUrl = registryUrl.endsWith("/api/registry")
74
- ? registryUrl
75
- : `${registryUrl}/api/registry`;
76
- const registryHost = fullRegistryUrl.replace(/^https?:/, "");
77
-
78
- // Default: scoped (only @lpm.dev packages through lpm.dev).
79
- // --proxy: route all npm traffic through lpm.dev (Pro/Org feature).
80
- let useScoped = !options?.proxy;
81
-
82
- // ── OIDC mode: exchange CI token for a real short-lived install token ──
83
- if (options?.oidc) {
84
- let oidcToken;
85
- try {
86
- oidcToken = await exchangeOidcInstallToken();
87
- } catch (err) {
88
- log.warn(`OIDC exchange failed: ${err.message}`);
89
- // biome-ignore lint/suspicious/noTemplateCurlyInString: literal placeholder name in user-facing message
90
- log.warn(
91
- "Falling back to ${LPM_TOKEN} environment variable placeholder.",
92
- );
93
- // Fall through to default mode
94
- }
95
-
96
- if (oidcToken) {
97
- let npmrcContent = "";
98
- if (fs.existsSync(npmrcPath)) {
99
- const existingContent = fs.readFileSync(npmrcPath, "utf8");
100
-
101
- // Detect custom default registry
102
- if (!useScoped) {
103
- const customRegistry = detectCustomRegistry(existingContent);
104
- if (customRegistry) {
105
- log.warn(
106
- `Found existing default registry: ${customRegistry.trim()}`,
107
- );
108
- log.warn("Using --scoped mode to avoid overriding it.");
109
- useScoped = true;
110
- }
111
- }
112
-
113
- npmrcContent = removeLpmLines(existingContent);
114
- }
115
-
116
- const registryLine = useScoped
117
- ? `@lpm.dev:registry=${fullRegistryUrl}`
118
- : `registry=${fullRegistryUrl}`;
119
-
120
- const lpmConfig = `# LPM Registry (OIDC — expires in 30 minutes)
121
- ${registryLine}
122
- ${registryHost}/:_authToken=${oidcToken}`;
123
-
124
- npmrcContent = npmrcContent
125
- ? `${npmrcContent}\n\n${lpmConfig}`
126
- : lpmConfig;
127
- fs.writeFileSync(npmrcPath, `${npmrcContent}\n`);
128
-
129
- p.note(
130
- `${registryLine}\nToken: OIDC read-only (30 min)`,
131
- ".npmrc configuration",
132
- );
133
-
134
- console.log("");
135
- log.success(".npmrc configured with OIDC read-only token.");
136
- log.info("Token expires in 30 minutes. No LPM_TOKEN secret needed.");
137
- if (!useScoped) {
138
- log.info("All packages (LPM + npm) will route through lpm.dev.");
139
- }
140
- console.log("");
141
-
142
- p.outro("Setup complete!");
143
- return;
144
- }
145
- }
146
-
147
- // ── Default mode: ${LPM_TOKEN} placeholder ──
148
-
149
- // Check for existing .npmrc
150
- let npmrcContent = "";
151
- if (fs.existsSync(npmrcPath)) {
152
- npmrcContent = fs.readFileSync(npmrcPath, "utf8");
153
-
154
- // Detect custom default registry
155
- if (!useScoped) {
156
- const customRegistry = detectCustomRegistry(npmrcContent);
157
- if (customRegistry) {
158
- log.warn(`Found existing default registry: ${customRegistry.trim()}`);
159
- log.warn("Using --scoped mode to avoid overriding it.");
160
- log.warn(
161
- "To use LPM as your default registry, remove the existing registry= line first.",
162
- );
163
- useScoped = true;
164
- }
165
- }
166
-
167
- // Check if already configured (both scoped and unscoped formats)
168
- if (
169
- npmrcContent.includes("@lpm.dev:registry") ||
170
- npmrcContent.match(/^registry=.*lpm\.dev/m)
171
- ) {
172
- const overwrite = await p.confirm({
173
- message: ".npmrc already has LPM configuration. Overwrite?",
174
- initialValue: false,
175
- });
176
-
177
- if (p.isCancel(overwrite) || !overwrite) {
178
- p.outro("Setup cancelled.");
179
- return;
180
- }
181
-
182
- npmrcContent = removeLpmLines(npmrcContent);
183
- }
184
- }
185
-
186
- const registryLine = useScoped
187
- ? `@lpm.dev:registry=${fullRegistryUrl}`
188
- : `registry=${fullRegistryUrl}`;
189
-
190
- // Add LPM registry config with placeholder
191
- const lpmConfig = `
192
- # LPM Registry
193
- ${registryLine}
194
- ${registryHost}/:_authToken=\${LPM_TOKEN}
195
- `.trim();
196
-
197
- npmrcContent = npmrcContent ? `${npmrcContent}\n\n${lpmConfig}` : lpmConfig;
198
- fs.writeFileSync(npmrcPath, `${npmrcContent}\n`);
199
-
200
- p.note(
201
- `${registryLine}\n${registryHost}/:_authToken=\${LPM_TOKEN}`,
202
- ".npmrc configuration",
203
- );
204
-
205
- console.log("");
206
- log.success(".npmrc configured for LPM registry.");
207
- if (useScoped) {
208
- log.info("Only @lpm.dev packages will route through lpm.dev.");
209
- } else {
210
- log.info("All packages (LPM + npm) will route through lpm.dev.");
211
- log.info("Note: First install may update resolved URLs in your lockfile.");
212
- }
213
- log.info("For local development: Run `lpm setup local` to generate a token.");
214
- log.info("For CI/CD: Set the LPM_TOKEN environment variable.");
215
- log.info(
216
- "For CI/CD with OIDC: Run `lpm setup ci --oidc` (no secrets needed).",
217
- );
218
- console.log("");
219
-
220
- p.outro("Setup complete!");
221
- }