@usehyper/cli 0.1.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/LICENSE +21 -0
- package/README.md +31 -0
- package/package.json +40 -0
- package/registry-sources/agent-rules/README.md +12 -0
- package/registry-sources/agent-rules/files/.cursor/rules/hyper.md +178 -0
- package/registry-sources/agent-rules/files/AGENTS.md +64 -0
- package/registry-sources/agent-rules/manifest.json +15 -0
- package/src/__tests__/add.test.ts +125 -0
- package/src/__tests__/cli.test.ts +50 -0
- package/src/__tests__/security.test.ts +101 -0
- package/src/args.ts +38 -0
- package/src/bin.ts +77 -0
- package/src/commands/add.ts +232 -0
- package/src/commands/bench.ts +185 -0
- package/src/commands/build.ts +146 -0
- package/src/commands/client.ts +78 -0
- package/src/commands/dev.ts +53 -0
- package/src/commands/diff.ts +80 -0
- package/src/commands/env.ts +92 -0
- package/src/commands/help.ts +42 -0
- package/src/commands/init.ts +119 -0
- package/src/commands/list.ts +46 -0
- package/src/commands/mcp.ts +51 -0
- package/src/commands/openapi.ts +50 -0
- package/src/commands/routes.ts +45 -0
- package/src/commands/security.ts +233 -0
- package/src/commands/test.ts +191 -0
- package/src/commands/typecheck.ts +19 -0
- package/src/commands/update.ts +91 -0
- package/src/commands/version.ts +16 -0
- package/src/config/index.ts +30 -0
- package/src/config/io.ts +112 -0
- package/src/config/tsconfig.ts +138 -0
- package/src/config/types.ts +63 -0
- package/src/entry.ts +42 -0
- package/src/index.ts +57 -0
- package/src/load-app.ts +89 -0
- package/src/registry/__tests__/env-writer.test.ts +83 -0
- package/src/registry/apply.ts +268 -0
- package/src/registry/client.ts +127 -0
- package/src/registry/env-writer.ts +135 -0
- package/src/registry/index.ts +18 -0
- package/src/registry/rewrite.ts +177 -0
- package/src/registry/snapshot.ts +1018 -0
- package/src/registry/types.ts +62 -0
- package/src/templates.ts +141 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File placement and import rewriting at install time.
|
|
3
|
+
*
|
|
4
|
+
* Two responsibilities:
|
|
5
|
+
*
|
|
6
|
+
* 1. `resolveTarget(file, name, baseDir)` decides where a manifest file
|
|
7
|
+
* lands in the user's project. Honors per-file `target` placeholders
|
|
8
|
+
* (`@base/`, `@root/`, `@cursor/`, `~/`) and falls back to
|
|
9
|
+
* `<baseDir>/<componentName>/<rel>` for package-derived files or the
|
|
10
|
+
* project root for hand-authored ones.
|
|
11
|
+
*
|
|
12
|
+
* 2. `rewriteFile(contents, ctx)` rewrites `@hyper/<x>` imports in source
|
|
13
|
+
* files to whatever alias the user configured:
|
|
14
|
+
* alias: "@hyper" — pass-through (most projects)
|
|
15
|
+
* alias: "@/lib/hyper" — `@hyper/core` → `@/lib/hyper/core`
|
|
16
|
+
* alias: "relative" — `@hyper/core` → computed relative path
|
|
17
|
+
*
|
|
18
|
+
* Subpath aliases declared by each component (`@hyper/core/bun` →
|
|
19
|
+
* `core/adapters/bun`) are applied via the `subpathsByComponent` map.
|
|
20
|
+
*
|
|
21
|
+
* Both are pure functions — no I/O.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { posix } from "node:path"
|
|
25
|
+
import type { RegistryFile } from "./types.ts"
|
|
26
|
+
|
|
27
|
+
const MANIFEST_ALIAS = "@hyper"
|
|
28
|
+
|
|
29
|
+
/** File extensions whose contents go through `rewriteFile`. */
|
|
30
|
+
const REWRITABLE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"])
|
|
31
|
+
|
|
32
|
+
export interface RewriteContext {
|
|
33
|
+
/** User-configured alias (`@hyper`, `@/lib/hyper`, or `"relative"`). */
|
|
34
|
+
readonly alias: string
|
|
35
|
+
/** Where the file ends up, relative to the project root. Used for relative mode. */
|
|
36
|
+
readonly targetPath: string
|
|
37
|
+
/** Where components are installed, relative to the project root. */
|
|
38
|
+
readonly baseDir: string
|
|
39
|
+
/**
|
|
40
|
+
* Subpath maps for every component being processed in this batch. Lets
|
|
41
|
+
* `@hyper/core/bun` resolve to `core/adapters/bun` (since that's where
|
|
42
|
+
* the file lives in the manifest).
|
|
43
|
+
*
|
|
44
|
+
* Already accounted for in the manifest itself for cross-component refs,
|
|
45
|
+
* but we re-apply for safety in case manifests evolve.
|
|
46
|
+
*/
|
|
47
|
+
readonly subpathsByComponent: ReadonlyMap<string, Readonly<Record<string, string>>>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function escapeRegex(s: string): string {
|
|
51
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const REWRITE_PATTERN = new RegExp(
|
|
55
|
+
// Capture: from / import / dynamic-import / triple-slash / require, then quoted spec.
|
|
56
|
+
`((?:from\\s*|import\\s*\\(?\\s*|require\\s*\\(\\s*)["'\`])${escapeRegex(MANIFEST_ALIAS)}\\/([a-z0-9-]+)((?:\\/[^"'\`]+)?)["'\`]`,
|
|
57
|
+
"g",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Rewrite the contents of one file according to the user's alias choice.
|
|
62
|
+
*
|
|
63
|
+
* Pure function — no I/O.
|
|
64
|
+
*/
|
|
65
|
+
export function rewriteFile(contents: string, ctx: RewriteContext): string {
|
|
66
|
+
if (ctx.alias === MANIFEST_ALIAS) return contents
|
|
67
|
+
|
|
68
|
+
return contents.replace(REWRITE_PATTERN, (_full, prefix: string, pkg: string, sub: string) => {
|
|
69
|
+
const trailingQuote = prefix.slice(-1) // " ' or `
|
|
70
|
+
const subPath = sub ? sub.slice(1) : ""
|
|
71
|
+
|
|
72
|
+
// Resolve subpath through the component's map if relevant.
|
|
73
|
+
const resolvedSub = subPath ? (ctx.subpathsByComponent.get(pkg)?.[subPath] ?? subPath) : ""
|
|
74
|
+
|
|
75
|
+
if (ctx.alias === "relative") {
|
|
76
|
+
const fileTarget = resolvedSub ? `${pkg}/${resolvedSub}` : `${pkg}/index`
|
|
77
|
+
const absInstalled = posix.join(ctx.baseDir, `${fileTarget}.ts`)
|
|
78
|
+
const fromDir = posix.dirname(toPosix(ctx.targetPath))
|
|
79
|
+
let relPath = posix.relative(fromDir, absInstalled)
|
|
80
|
+
if (!relPath.startsWith(".")) relPath = `./${relPath}`
|
|
81
|
+
return `${prefix}${relPath}${trailingQuote}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Custom alias — `@/lib/hyper`, `~/hyper`, etc.
|
|
85
|
+
const tail = resolvedSub ? `/${pkg}/${resolvedSub}` : `/${pkg}`
|
|
86
|
+
return `${prefix}${ctx.alias}${tail}${trailingQuote}`
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function toPosix(p: string): string {
|
|
91
|
+
return p.replace(/\\/g, "/")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface ResolvedTarget {
|
|
95
|
+
/** Project-relative path where this file should land. */
|
|
96
|
+
readonly relPath: string
|
|
97
|
+
/**
|
|
98
|
+
* Whether the contents should pass through `rewriteFile` (alias rewriting).
|
|
99
|
+
* False for markdown, JSON, env files, and anything else that isn't source.
|
|
100
|
+
*/
|
|
101
|
+
readonly rewriteImports: boolean
|
|
102
|
+
/**
|
|
103
|
+
* Set when the manifest used a placeholder we don't recognize. The CLI
|
|
104
|
+
* surfaces this as a warning and falls back to a literal path.
|
|
105
|
+
*/
|
|
106
|
+
readonly warning?: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolve a manifest file to a project-relative install path.
|
|
111
|
+
*
|
|
112
|
+
* Honors per-file `target` overrides with a small set of placeholders
|
|
113
|
+
* (`@base`, `@root`, `@cursor`, `~`). Unrecognized `@<word>/` prefixes are
|
|
114
|
+
* passed through verbatim with a warning. Falls back to today's behavior
|
|
115
|
+
* when no `target` is set:
|
|
116
|
+
*
|
|
117
|
+
* - paths starting with `<componentName>/` go under `<baseDir>/`
|
|
118
|
+
* - anything else is a project-root drop (e.g. `AGENTS.md`)
|
|
119
|
+
*/
|
|
120
|
+
export function resolveTarget(
|
|
121
|
+
file: RegistryFile,
|
|
122
|
+
componentName: string,
|
|
123
|
+
baseDir: string,
|
|
124
|
+
): ResolvedTarget {
|
|
125
|
+
const target = file.target?.trim()
|
|
126
|
+
if (target) {
|
|
127
|
+
const expansion = expandPlaceholder(target, baseDir)
|
|
128
|
+
return {
|
|
129
|
+
relPath: posix.normalize(expansion.path),
|
|
130
|
+
rewriteImports: shouldRewriteByExt(expansion.path),
|
|
131
|
+
...(expansion.warning !== undefined && { warning: expansion.warning }),
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Fallback: package-derived files start with `<componentName>/` and live
|
|
135
|
+
// under `<baseDir>/`. Hand-authored files without a target stay at the
|
|
136
|
+
// project root.
|
|
137
|
+
if (file.path.startsWith(`${componentName}/`)) {
|
|
138
|
+
return {
|
|
139
|
+
relPath: posix.join(baseDir, file.path),
|
|
140
|
+
rewriteImports: shouldRewriteByExt(file.path),
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
relPath: file.path,
|
|
145
|
+
rewriteImports: shouldRewriteByExt(file.path),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface PlaceholderExpansion {
|
|
150
|
+
readonly path: string
|
|
151
|
+
readonly warning?: string
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function expandPlaceholder(target: string, baseDir: string): PlaceholderExpansion {
|
|
155
|
+
if (target.startsWith("@base/"))
|
|
156
|
+
return { path: posix.join(baseDir, target.slice("@base/".length)) }
|
|
157
|
+
if (target === "@base") return { path: baseDir }
|
|
158
|
+
if (target.startsWith("@root/")) return { path: target.slice("@root/".length) }
|
|
159
|
+
if (target.startsWith("~/")) return { path: target.slice(2) }
|
|
160
|
+
if (target.startsWith("@cursor/"))
|
|
161
|
+
return { path: posix.join(".cursor", target.slice("@cursor/".length)) }
|
|
162
|
+
// Unknown placeholder — pass through, warn the caller.
|
|
163
|
+
if (/^@[a-z][a-z0-9_-]*\//i.test(target)) {
|
|
164
|
+
return {
|
|
165
|
+
path: target,
|
|
166
|
+
warning: `Unknown target placeholder in "${target}" — writing to literal path. Known placeholders: @base, @root, @cursor, ~`,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { path: target }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function shouldRewriteByExt(path: string): boolean {
|
|
173
|
+
const dot = path.lastIndexOf(".")
|
|
174
|
+
if (dot < 0) return false
|
|
175
|
+
const ext = path.slice(dot).toLowerCase()
|
|
176
|
+
return REWRITABLE_EXTS.has(ext)
|
|
177
|
+
}
|