@lpm-registry/cli 0.2.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +15 -0
  3. package/README.md +406 -0
  4. package/bin/lpm.js +334 -0
  5. package/index.d.ts +131 -0
  6. package/index.js +31 -0
  7. package/lib/api.js +324 -0
  8. package/lib/commands/add.js +1217 -0
  9. package/lib/commands/audit.js +283 -0
  10. package/lib/commands/cache.js +209 -0
  11. package/lib/commands/check-name.js +112 -0
  12. package/lib/commands/config.js +174 -0
  13. package/lib/commands/doctor.js +142 -0
  14. package/lib/commands/info.js +215 -0
  15. package/lib/commands/init.js +146 -0
  16. package/lib/commands/install.js +217 -0
  17. package/lib/commands/login.js +547 -0
  18. package/lib/commands/logout.js +94 -0
  19. package/lib/commands/marketplace-compare.js +164 -0
  20. package/lib/commands/marketplace-earnings.js +89 -0
  21. package/lib/commands/mcp-setup.js +363 -0
  22. package/lib/commands/open.js +82 -0
  23. package/lib/commands/outdated.js +291 -0
  24. package/lib/commands/pool-stats.js +100 -0
  25. package/lib/commands/publish.js +707 -0
  26. package/lib/commands/quality.js +211 -0
  27. package/lib/commands/remove.js +82 -0
  28. package/lib/commands/run.js +14 -0
  29. package/lib/commands/search.js +143 -0
  30. package/lib/commands/setup.js +92 -0
  31. package/lib/commands/skills.js +863 -0
  32. package/lib/commands/token-rotate.js +25 -0
  33. package/lib/commands/whoami.js +129 -0
  34. package/lib/config.js +240 -0
  35. package/lib/constants.js +190 -0
  36. package/lib/ecosystem.js +501 -0
  37. package/lib/editors.js +215 -0
  38. package/lib/import-rewriter.js +364 -0
  39. package/lib/install-targets/mcp-server.js +245 -0
  40. package/lib/install-targets/vscode-extension.js +178 -0
  41. package/lib/install-targets.js +82 -0
  42. package/lib/integrity.js +179 -0
  43. package/lib/lpm-config-prompts.js +102 -0
  44. package/lib/lpm-config.js +408 -0
  45. package/lib/project-utils.js +152 -0
  46. package/lib/quality/checks.js +654 -0
  47. package/lib/quality/display.js +139 -0
  48. package/lib/quality/score.js +115 -0
  49. package/lib/quality/swift-checks.js +447 -0
  50. package/lib/safe-path.js +180 -0
  51. package/lib/secure-store.js +288 -0
  52. package/lib/swift-project.js +637 -0
  53. package/lib/ui.js +40 -0
  54. package/package.json +74 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Config Command
3
+ *
4
+ * Manage CLI configuration values.
5
+ *
6
+ * Usage:
7
+ * lpm config list List all configuration values
8
+ * lpm config get <key> Get a specific config value
9
+ * lpm config set <key> <value> Set a config value
10
+ * lpm config delete <key> Delete a config value (reset to default)
11
+ *
12
+ * Configurable Keys:
13
+ * registry - Registry URL (default: https://lpm.dev)
14
+ * timeout - Request timeout in milliseconds (default: 30000)
15
+ * retries - Maximum retry attempts (default: 3)
16
+ *
17
+ * @module cli/lib/commands/config
18
+ */
19
+
20
+ import chalk from "chalk"
21
+ import {
22
+ deleteConfigValue,
23
+ getAllConfig,
24
+ getConfigValue,
25
+ setConfigValue,
26
+ } from "../config.js"
27
+
28
+ /**
29
+ * List all configuration values.
30
+ */
31
+ async function listConfig() {
32
+ console.log(chalk.bold("\nLPM Configuration:\n"))
33
+
34
+ const config = await getAllConfig()
35
+
36
+ // Format and display each config value
37
+ const entries = [
38
+ ["Registry URL", config.registryUrl],
39
+ ["Request Timeout", `${config.timeout}ms`],
40
+ ["Max Retries", config.retries],
41
+ ["Secure Storage", config.secureStorage],
42
+ [
43
+ "Authenticated",
44
+ config.authenticated ? chalk.green("Yes") : chalk.dim("No"),
45
+ ],
46
+ ]
47
+
48
+ const maxKeyLength = Math.max(...entries.map(([key]) => key.length))
49
+
50
+ for (const [key, value] of entries) {
51
+ const paddedKey = key.padEnd(maxKeyLength)
52
+ console.log(` ${chalk.cyan(paddedKey)} ${value}`)
53
+ }
54
+
55
+ console.log("")
56
+ }
57
+
58
+ /**
59
+ * Get a specific configuration value.
60
+ * @param {string} key - The config key to get
61
+ */
62
+ function getConfig(key) {
63
+ const value = getConfigValue(key)
64
+
65
+ if (value === undefined) {
66
+ console.log(chalk.yellow(`Configuration key '${key}' is not set.`))
67
+ return
68
+ }
69
+
70
+ console.log(value)
71
+ }
72
+
73
+ /**
74
+ * Set a configuration value.
75
+ * @param {string} key - The config key to set
76
+ * @param {string} value - The value to set
77
+ */
78
+ function setConfig(key, value) {
79
+ // Validate known keys
80
+ const numericKeys = ["timeout", "retries"]
81
+ if (numericKeys.includes(key)) {
82
+ const numValue = parseInt(value, 10)
83
+ if (Number.isNaN(numValue) || numValue < 0) {
84
+ console.error(chalk.red(`Error: '${key}' must be a positive number.`))
85
+ process.exit(1)
86
+ }
87
+ }
88
+
89
+ // Validate registry URL
90
+ if (key === "registry" || key === "registryUrl") {
91
+ try {
92
+ new URL(value)
93
+ } catch {
94
+ console.error(chalk.red(`Error: '${value}' is not a valid URL.`))
95
+ process.exit(1)
96
+ }
97
+ }
98
+
99
+ const success = setConfigValue(key, value)
100
+
101
+ if (success) {
102
+ console.log(chalk.green(`Set ${key} = ${value}`))
103
+ } else {
104
+ console.error(chalk.red(`Failed to set '${key}'.`))
105
+ process.exit(1)
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Delete a configuration value (reset to default).
111
+ * @param {string} key - The config key to delete
112
+ */
113
+ function deleteConfig(key) {
114
+ const success = deleteConfigValue(key)
115
+
116
+ if (success) {
117
+ console.log(chalk.green(`Reset '${key}' to default value.`))
118
+ } else {
119
+ console.error(chalk.red(`Failed to delete '${key}'.`))
120
+ process.exit(1)
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Execute the config command.
126
+ *
127
+ * @param {string} action - The action to perform (list, get, set, delete)
128
+ * @param {string} [key] - The config key
129
+ * @param {string} [value] - The value to set
130
+ */
131
+ export async function config(action, key, value) {
132
+ switch (action) {
133
+ case "list":
134
+ case undefined:
135
+ await listConfig()
136
+ break
137
+
138
+ case "get":
139
+ if (!key) {
140
+ console.error(chalk.red("Error: Key required."))
141
+ console.log(chalk.dim("Usage: lpm config get <key>"))
142
+ process.exit(1)
143
+ }
144
+ getConfig(key)
145
+ break
146
+
147
+ case "set":
148
+ if (!key || value === undefined) {
149
+ console.error(chalk.red("Error: Key and value required."))
150
+ console.log(chalk.dim("Usage: lpm config set <key> <value>"))
151
+ process.exit(1)
152
+ }
153
+ setConfig(key, value)
154
+ break
155
+
156
+ case "delete":
157
+ case "rm":
158
+ case "remove":
159
+ if (!key) {
160
+ console.error(chalk.red("Error: Key required."))
161
+ console.log(chalk.dim("Usage: lpm config delete <key>"))
162
+ process.exit(1)
163
+ }
164
+ deleteConfig(key)
165
+ break
166
+
167
+ default:
168
+ console.error(chalk.red(`Unknown action: ${action}`))
169
+ console.log(chalk.dim("Available actions: list, get, set, delete"))
170
+ process.exit(1)
171
+ }
172
+ }
173
+
174
+ export default config
@@ -0,0 +1,142 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import chalk from "chalk"
4
+ import { getRegistryUrl, getToken } from "../config.js"
5
+ import { createSpinner, log, printHeader } from "../ui.js"
6
+
7
+ export async function doctor() {
8
+ printHeader()
9
+ log.info("Running health checks...\n")
10
+
11
+ const checks = []
12
+
13
+ // 1. Check Auth
14
+ const spinnerAuth = createSpinner("Checking authentication...").start()
15
+ const token = await getToken()
16
+ if (token) {
17
+ spinnerAuth.succeed("Authentication token found.")
18
+ checks.push({ name: "Auth Token", status: "ok" })
19
+ } else {
20
+ spinnerAuth.fail("No authentication token found. Run `lpm login`.")
21
+ checks.push({ name: "Auth Token", status: "fail" })
22
+ }
23
+
24
+ // 2. Check Registry Reachability
25
+ const spinnerApi = createSpinner("Checking registry connection...").start()
26
+ try {
27
+ // Assuming we have a health endpoint or similar. Using the registry URL from config or default.
28
+ const registryUrl = getRegistryUrl()
29
+ const res = await fetch(`${registryUrl}/api/registry/health`)
30
+ if (res.ok) {
31
+ spinnerApi.succeed(`Registry reachable at ${registryUrl}`)
32
+ checks.push({ name: "Registry API", status: "ok" })
33
+ } else {
34
+ spinnerApi.fail(`Registry returned status ${res.status}`)
35
+ checks.push({ name: "Registry API", status: "fail" })
36
+ }
37
+ } catch (err) {
38
+ spinnerApi.fail(`Could not connect to registry: ${err.message}`)
39
+ checks.push({ name: "Registry API", status: "fail" })
40
+ }
41
+
42
+ // 3. Check Quota (via whoami)
43
+ if (token) {
44
+ const spinnerQuota = createSpinner("Checking account quota...").start()
45
+ try {
46
+ // We can reuse the whoami logic or fetch directly
47
+ // Since we are in doctor, let's fetch directly using the token
48
+ const registryUrl = getRegistryUrl()
49
+ const res = await fetch(`${registryUrl}/api/registry/-/whoami`, {
50
+ headers: {
51
+ Authorization: `Bearer ${token}`,
52
+ },
53
+ })
54
+
55
+ if (res.ok) {
56
+ const data = await res.json()
57
+ if (data.plan_tier) {
58
+ const limits = data.limits || {}
59
+ let isOverLimit = false
60
+ const statusMsg = `Plan: ${data.plan_tier.toUpperCase()}`
61
+
62
+ if (limits.storageBytes) {
63
+ if (data.usage.storage_bytes > limits.storageBytes)
64
+ isOverLimit = true
65
+ }
66
+ if (
67
+ limits.privatePackages &&
68
+ limits.privatePackages !== Number.POSITIVE_INFINITY &&
69
+ limits.privatePackages !== null
70
+ ) {
71
+ if (data.usage.private_packages > limits.privatePackages)
72
+ isOverLimit = true
73
+ }
74
+
75
+ if (isOverLimit) {
76
+ spinnerQuota.fail(`${statusMsg} | OVER LIMIT`)
77
+ checks.push({ name: "Account Quota", status: "fail" })
78
+ } else {
79
+ spinnerQuota.succeed(
80
+ `${statusMsg} | Storage: ${(data.usage.storage_bytes / 1024 / 1024).toFixed(0)}MB`,
81
+ )
82
+ checks.push({ name: "Account Quota", status: "ok" })
83
+ }
84
+ } else {
85
+ spinnerQuota.info("Could not retrieve detailed quota info.")
86
+ checks.push({ name: "Account Quota", status: "info" })
87
+ }
88
+ } else {
89
+ spinnerQuota.warn("Could not fetch account details.")
90
+ checks.push({ name: "Account Quota", status: "warn" })
91
+ }
92
+ } catch (err) {
93
+ spinnerQuota.warn(`Quota check failed: ${err.message}`)
94
+ checks.push({ name: "Account Quota", status: "warn" })
95
+ }
96
+ }
97
+
98
+ // 4. Check .npmrc
99
+ const spinnerNpmrc = createSpinner("Checking .npmrc configuration...").start()
100
+ const npmrcPath = path.join(process.cwd(), ".npmrc")
101
+ if (fs.existsSync(npmrcPath)) {
102
+ const content = fs.readFileSync(npmrcPath, "utf-8")
103
+ if (content.includes("registry=")) {
104
+ spinnerNpmrc.succeed(".npmrc found and configured.")
105
+ checks.push({ name: ".npmrc", status: "ok" })
106
+ } else {
107
+ spinnerNpmrc.warn(".npmrc found but might be missing registry config.")
108
+ checks.push({ name: ".npmrc", status: "warn" })
109
+ }
110
+ } else {
111
+ spinnerNpmrc.info("No local .npmrc found (global config might be used).")
112
+ checks.push({ name: ".npmrc", status: "info" })
113
+ }
114
+
115
+ console.log("\nSummary:")
116
+ checks.forEach(c => {
117
+ const symbol =
118
+ c.status === "ok"
119
+ ? "✔"
120
+ : c.status === "fail"
121
+ ? "✖"
122
+ : c.status === "warn"
123
+ ? "⚠"
124
+ : "ℹ"
125
+ const color =
126
+ c.status === "ok"
127
+ ? chalk.green
128
+ : c.status === "fail"
129
+ ? chalk.red
130
+ : c.status === "warn"
131
+ ? chalk.yellow
132
+ : chalk.blue
133
+ console.log(color(`${symbol} ${c.name}`))
134
+ })
135
+
136
+ if (checks.some(c => c.status === "fail")) {
137
+ console.log(chalk.red("\nSome checks failed. Please fix the issues above."))
138
+ process.exit(1)
139
+ } else {
140
+ console.log(chalk.green("\nAll systems operational."))
141
+ }
142
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Info Command
3
+ *
4
+ * Display detailed information about a package.
5
+ *
6
+ * Usage:
7
+ * lpm info <package> [options]
8
+ *
9
+ * Options:
10
+ * --json Output in JSON format
11
+ * --versions Show all versions
12
+ *
13
+ * @module cli/lib/commands/info
14
+ */
15
+
16
+ import chalk from "chalk"
17
+ import ora from "ora"
18
+ import { get } from "../api.js"
19
+
20
+ /**
21
+ * Parse a package specifier.
22
+ * @param {string} specifier - Package name (e.g., '@scope/name', 'name', 'name@1.0.0')
23
+ * @returns {{ name: string, scope?: string, version?: string }}
24
+ */
25
+ function parsePackageSpecifier(specifier) {
26
+ let name = specifier
27
+ let scope
28
+ let version
29
+
30
+ // Handle version specifier
31
+ const versionMatch = name.match(/^(.+)@(\d+\.\d+\.\d+.*)$/)
32
+ if (versionMatch) {
33
+ name = versionMatch[1]
34
+ version = versionMatch[2]
35
+ }
36
+
37
+ // Handle scope
38
+ if (name.startsWith("@")) {
39
+ const scopeMatch = name.match(/^@([^/]+)\/(.+)$/)
40
+ if (scopeMatch) {
41
+ scope = scopeMatch[1]
42
+ name = scopeMatch[2]
43
+ }
44
+ }
45
+
46
+ return { name, scope, version }
47
+ }
48
+
49
+ /**
50
+ * Format a date for display.
51
+ * @param {string} dateStr
52
+ * @returns {string}
53
+ */
54
+ function formatDate(dateStr) {
55
+ const date = new Date(dateStr)
56
+ return date.toLocaleDateString("en-US", {
57
+ year: "numeric",
58
+ month: "short",
59
+ day: "numeric",
60
+ })
61
+ }
62
+
63
+ /**
64
+ * Execute the info command.
65
+ *
66
+ * @param {string} packageSpec - Package specifier
67
+ * @param {Object} options - Command options
68
+ * @param {boolean} [options.json] - Output as JSON
69
+ * @param {boolean} [options.versions] - Show all versions
70
+ */
71
+ export async function info(packageSpec, options = {}) {
72
+ if (!packageSpec || packageSpec.trim() === "") {
73
+ console.error(chalk.red("Error: Package name required."))
74
+ console.log(chalk.dim("Usage: lpm info <package>"))
75
+ process.exit(1)
76
+ }
77
+
78
+ const { name, scope, version } = parsePackageSpecifier(packageSpec)
79
+ const packagePath = scope ? `@${scope}/${name}` : name
80
+
81
+ const spinner = ora(`Fetching info for ${packagePath}...`).start()
82
+
83
+ try {
84
+ // Registry uses NPM-compatible paths: /@scope/pkg or /@scope/pkg/version
85
+ const url = version
86
+ ? `/${encodeURIComponent(packagePath)}/${version}`
87
+ : `/${encodeURIComponent(packagePath)}`
88
+
89
+ const response = await get(url, {
90
+ onRetry: (attempt, max) => {
91
+ spinner.text = `Fetching (retry ${attempt}/${max})...`
92
+ },
93
+ })
94
+
95
+ if (!response.ok) {
96
+ if (response.status === 404) {
97
+ throw new Error(`Package "${packagePath}" not found.`)
98
+ }
99
+ const data = await response.json().catch(() => ({}))
100
+ throw new Error(data.error || `Failed to fetch package info.`)
101
+ }
102
+
103
+ const pkg = await response.json()
104
+
105
+ spinner.stop()
106
+
107
+ if (options.json) {
108
+ console.log(JSON.stringify(pkg, null, 2))
109
+ return
110
+ }
111
+
112
+ // Get latest version data from versions object
113
+ const latestVersionTag = pkg["dist-tags"]?.latest
114
+ const latestVersionData = latestVersionTag
115
+ ? pkg.versions?.[latestVersionTag]
116
+ : null
117
+
118
+ // Display package info
119
+ const fullName = pkg.name
120
+
121
+ console.log("")
122
+ console.log(
123
+ chalk.bold.cyan(fullName) +
124
+ chalk.dim(`@${pkg.latestVersion || latestVersionTag || "no versions"}`),
125
+ )
126
+ console.log("")
127
+
128
+ if (pkg.description) {
129
+ console.log(chalk.white(pkg.description))
130
+ console.log("")
131
+ }
132
+
133
+ // Metadata table
134
+ const metadata = [
135
+ ["Latest Version", pkg.latestVersion || latestVersionTag || "N/A"],
136
+ ["License", latestVersionData?.license || pkg.license || "N/A"],
137
+ ["Author", latestVersionData?.author || pkg.author || "N/A"],
138
+ ["Published", pkg.publishedAt ? formatDate(pkg.publishedAt) : "N/A"],
139
+ ["Downloads", (pkg.downloads ?? 0).toLocaleString()],
140
+ ]
141
+
142
+ if (latestVersionData?.repository || pkg.repository) {
143
+ metadata.push([
144
+ "Repository",
145
+ latestVersionData?.repository || pkg.repository,
146
+ ])
147
+ }
148
+
149
+ if (latestVersionData?.homepage || pkg.homepage) {
150
+ metadata.push(["Homepage", latestVersionData?.homepage || pkg.homepage])
151
+ }
152
+
153
+ const maxKeyLength = Math.max(...metadata.map(([key]) => key.length))
154
+
155
+ for (const [key, value] of metadata) {
156
+ const paddedKey = key.padEnd(maxKeyLength)
157
+ console.log(` ${chalk.dim(paddedKey)} ${value}`)
158
+ }
159
+
160
+ console.log("")
161
+
162
+ // Dependencies from latest version
163
+ const deps = latestVersionData?.dependencies || pkg.dependencies
164
+ if (deps && Object.keys(deps).length > 0) {
165
+ console.log(chalk.bold("Dependencies:"))
166
+ for (const [dep, ver] of Object.entries(deps)) {
167
+ console.log(` ${chalk.cyan(dep)} ${chalk.dim(ver)}`)
168
+ }
169
+ console.log("")
170
+ }
171
+
172
+ // Show versions if requested (versions is an object, not array)
173
+ if (
174
+ options.allVersions &&
175
+ pkg.versions &&
176
+ Object.keys(pkg.versions).length > 0
177
+ ) {
178
+ console.log(chalk.bold("Versions:"))
179
+ const versionList = Object.keys(pkg.versions).reverse().slice(0, 20)
180
+ for (const v of versionList) {
181
+ const date = pkg.time?.[v] ? formatDate(pkg.time[v]) : ""
182
+ console.log(` ${chalk.green(v)} ${chalk.dim(date)}`)
183
+ }
184
+ if (Object.keys(pkg.versions).length > 20) {
185
+ console.log(
186
+ chalk.dim(
187
+ ` ... and ${Object.keys(pkg.versions).length - 20} more versions`,
188
+ ),
189
+ )
190
+ }
191
+ console.log("")
192
+ }
193
+
194
+ // Keywords
195
+ const keywords = latestVersionData?.keywords || pkg.keywords
196
+ if (keywords && keywords.length > 0) {
197
+ console.log(
198
+ chalk.bold("Keywords:") +
199
+ " " +
200
+ keywords.map(k => chalk.dim(k)).join(", "),
201
+ )
202
+ console.log("")
203
+ }
204
+
205
+ // Install hint
206
+ console.log(chalk.dim(`Install: lpm install ${fullName}`))
207
+ console.log("")
208
+ } catch (error) {
209
+ spinner.fail(chalk.red("Failed to fetch package info."))
210
+ console.error(chalk.red(` ${error.message}`))
211
+ process.exit(1)
212
+ }
213
+ }
214
+
215
+ export default info
@@ -0,0 +1,146 @@
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 { printHeader } from "../ui.js"
7
+ import { setup } from "./setup.js"
8
+
9
+ /**
10
+ * Parse package name in the @lpm.dev/owner.package format
11
+ */
12
+ function parsePackageName(name) {
13
+ if (!name.startsWith("@lpm.dev/")) {
14
+ return { error: "Package name must start with @lpm.dev/" }
15
+ }
16
+
17
+ const nameWithOwner = name.replace("@lpm.dev/", "")
18
+ const dotIndex = nameWithOwner.indexOf(".")
19
+
20
+ if (dotIndex === -1) {
21
+ return { error: "Format: @lpm.dev/owner.package-name" }
22
+ }
23
+
24
+ const owner = nameWithOwner.substring(0, dotIndex)
25
+ const pkgName = nameWithOwner.substring(dotIndex + 1)
26
+
27
+ // Validate owner format
28
+ if (!/^[a-z][a-z0-9-]*$/.test(owner)) {
29
+ return {
30
+ error:
31
+ "Owner must start with a letter and contain only lowercase letters, numbers, and hyphens",
32
+ }
33
+ }
34
+
35
+ // Validate package name format
36
+ if (!/^[a-z][a-z0-9-]*$/.test(pkgName)) {
37
+ return {
38
+ error:
39
+ "Package name must start with a letter and contain only lowercase letters, numbers, and hyphens",
40
+ }
41
+ }
42
+
43
+ return { owner, name: pkgName }
44
+ }
45
+
46
+ export async function init() {
47
+ printHeader()
48
+
49
+ p.intro(chalk.bgCyan(chalk.black(" lpm init ")))
50
+
51
+ const project = await p.group(
52
+ {
53
+ name: () =>
54
+ p.text({
55
+ message: "What is the name of your package?",
56
+ placeholder: "@lpm.dev/username.package-name",
57
+ validate: value => {
58
+ if (!value) return "Name is required"
59
+ const parsed = parsePackageName(value)
60
+ if (parsed.error) return parsed.error
61
+ },
62
+ }),
63
+ version: () =>
64
+ p.text({
65
+ message: "Version",
66
+ initialValue: "0.1.0",
67
+ }),
68
+ description: () =>
69
+ p.text({
70
+ message: "Description",
71
+ placeholder: "A brief description of your package",
72
+ }),
73
+ entry: () =>
74
+ p.text({
75
+ message: "Entry point",
76
+ initialValue: "index.js",
77
+ }),
78
+ license: () =>
79
+ p.select({
80
+ message: "License type",
81
+ options: [
82
+ { value: "ISC", label: "ISC" },
83
+ { value: "MIT", label: "MIT" },
84
+ { value: "UNLICENSED", label: "UNLICENSED (Private/Proprietary)" },
85
+ ],
86
+ }),
87
+ confirm: () =>
88
+ p.confirm({
89
+ message: "Create package.json?",
90
+ initialValue: true,
91
+ }),
92
+ },
93
+ {
94
+ onCancel: () => {
95
+ p.cancel("Operation cancelled.")
96
+ process.exit(0)
97
+ },
98
+ },
99
+ )
100
+
101
+ if (project.confirm) {
102
+ const registryUrl = getRegistryUrl()
103
+ const packageJson = {
104
+ name: project.name,
105
+ version: project.version,
106
+ description: project.description,
107
+ main: project.entry,
108
+ scripts: {
109
+ test: 'echo "Error: no test specified" && exit 1',
110
+ },
111
+ author: "",
112
+ license: project.license,
113
+ publishConfig: {
114
+ registry: `${registryUrl}/api/registry/`,
115
+ },
116
+ }
117
+
118
+ const filePath = path.join(process.cwd(), "package.json")
119
+ fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2))
120
+
121
+ p.note(JSON.stringify(packageJson, null, 2), "package.json")
122
+
123
+ // Offer to setup .npmrc
124
+ const npmrcPath = path.join(process.cwd(), ".npmrc")
125
+ if (
126
+ !fs.existsSync(npmrcPath) ||
127
+ !fs.readFileSync(npmrcPath, "utf8").includes("@lpm.dev:registry")
128
+ ) {
129
+ const setupNpmrc = await p.confirm({
130
+ message: "Create .npmrc for local development?",
131
+ initialValue: true,
132
+ })
133
+
134
+ if (!p.isCancel(setupNpmrc) && setupNpmrc) {
135
+ await setup({ registry: registryUrl })
136
+ return
137
+ }
138
+ }
139
+
140
+ p.outro(
141
+ `Package initialized! Run ${chalk.cyan("lpm publish")} to push to the registry.`,
142
+ )
143
+ } else {
144
+ p.outro("Cancelled.")
145
+ }
146
+ }