Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to the `stampui` CLI. Dates follow the npm publish history.

## Unreleased

- `add` and `update` now fail fast outside a project root with a package.json, with a `--force` escape hatch for intentional bare-directory use
- CLI `--version` now reads from package.json instead of a hardcoded string (previously reported 1.2.0 on the 1.2.1 build)
- Pure helpers (config, lock file, license key validation) extracted to `src/lib.ts` with a vitest test suite
- CI (typecheck, build, test) on GitHub Actions
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ npx stampui add faq-accordion

After installing it prints the files written, any npm dependencies the block needs, and the import path.

Run it from a project root with a `package.json`; use `--force` only if you intentionally want to stamp into a bare directory.

### `stampui list`

Lists every block in the registry, grouped by category. Filter by tier:
Expand All @@ -67,6 +69,7 @@ Checks installed blocks (from `stampui.lock.json`) against the latest registry v
```bash
npx stampui update # check everything
npx stampui update hero-section
npx stampui update --force # allow updating in a bare directory
```

Note: `update` overwrites the block's files with the latest upstream version. If you have local edits, review them with `git diff` before committing (a `stampui diff` command is on the roadmap, see issues).
Expand Down
21 changes: 19 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fs from "fs"
import path from "path"
import {
ProjectConfig,
hasPackageJson,
readPackageJson,
readLockFile,
writeLockFile,
Expand Down Expand Up @@ -141,9 +142,17 @@ cli

cli
.command("add <block>", "Stamp a block into your project")
.action(async (block: string) => {
.option("--force", "Allow installing into a directory without package.json")
.action(async (block: string, options: { force?: boolean }) => {
console.log(chalk.bold("\nStampUI\n"))

if (!options.force && !hasPackageJson()) {
console.log(chalk.red(`× No package.json found in ${process.cwd()}.`))
console.log(chalk.dim(` Run ${chalk.cyan("stampui doctor")} for project setup checks.`))
console.log(chalk.dim(` Use ${chalk.cyan("--force")} to stamp into this directory anyway.`))
process.exit(1)
}

const blockData = manifests[block]
if (!blockData) {
console.log(chalk.red(`× Block '${block}' not found.`))
Expand Down Expand Up @@ -443,9 +452,17 @@ cli

cli
.command("update [block]", "Update installed blocks to the latest version")
.action(async (block?: string) => {
.option("--force", "Allow updating in a directory without package.json")
.action(async (block: string | undefined, options: { force?: boolean }) => {
console.log(chalk.bold("\nStampUI Update\n"))

if (!options.force && !hasPackageJson()) {
console.log(chalk.red(`× No package.json found in ${process.cwd()}.`))
console.log(chalk.dim(` Run ${chalk.cyan("stampui doctor")} for project setup checks.`))
console.log(chalk.dim(` Use ${chalk.cyan("--force")} to update in this directory anyway.`))
process.exit(1)
}

const lock = readLockFile()

if (Object.keys(lock).length === 0) {
Expand Down
4 changes: 4 additions & 0 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export function readPackageJson(cwd: string = process.cwd()): Record<string, unk
}
}

export function hasPackageJson(cwd: string = process.cwd()): boolean {
return fs.existsSync(path.join(cwd, "package.json"))
}

// ── Lock file ─────────────────────────────────────────────────────────────────

export interface LockEntry {
Expand Down
12 changes: 12 additions & 0 deletions tests/lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import os from "os"
import path from "path"
import {
DEFAULT_PROJECT_CONFIG,
hasPackageJson,
readProjectConfig,
readPackageJson,
readLockFile,
Expand Down Expand Up @@ -73,6 +74,17 @@ describe("readPackageJson", () => {
})
})

describe("hasPackageJson", () => {
it("returns false when package.json is missing", () => {
expect(hasPackageJson(tmp)).toBe(false)
})

it("returns true when package.json exists", () => {
fs.writeFileSync(path.join(tmp, "package.json"), JSON.stringify({ name: "demo-app" }))
expect(hasPackageJson(tmp)).toBe(true)
})
})

describe("lock file", () => {
it("returns an empty lock when stampui.lock.json is missing", () => {
expect(readLockFile(tmp)).toEqual({})
Expand Down