Skip to content

Commit c5b9015

Browse files
committed
feat: add CONTRIBUTING.md and update project documentation
- Introduced a new CONTRIBUTING.md file to guide contributors on local development, project layout, and release processes. - Updated README to reference the new CONTRIBUTING.md for development setup and project structure. - Modified package.json to include the repository URL and adjusted publish configuration for npm. - Enhanced release workflow to support trusted publishing with OIDC for npm.
1 parent a54d2e2 commit c5b9015

6 files changed

Lines changed: 119 additions & 99 deletions

File tree

.github/workflows/release.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919

2020
permissions:
2121
contents: write
22+
id-token: write
2223

2324
steps:
2425
- name: Checkout repository
@@ -68,6 +69,11 @@ jobs:
6869
generate_release_notes: true
6970

7071
- name: Publish to GitHub Packages
71-
run: npm publish
72+
run: npm publish --registry=https://npm.pkg.github.com
7273
env:
7374
NODE_AUTH_TOKEN: ${{ secrets.GH_PACKAGES_TOKEN }}
75+
76+
- name: Publish to npm (trusted publishing / OIDC)
77+
run: |
78+
npm install -g npm@latest
79+
npm publish --registry=https://registry.npmjs.org --provenance

CONTRIBUTING.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Contributing to Ensemble CLI
2+
3+
This document is for people working on the CLI itself. If you only use `ensemble` in your projects, see the [README](README.md).
4+
5+
## Local development
6+
7+
```bash
8+
git clone https://github.com/EnsembleUI/ensemble-cli
9+
cd ensemble-cli
10+
npm install
11+
npm run build
12+
npm link # optional: use the CLI globally from this checkout
13+
```
14+
15+
### Scripts
16+
17+
| Command | Description |
18+
| ---------------------- | ----------------------------- |
19+
| `npm run dev` | Run the CLI with `ts-node` |
20+
| `npm run build` | Compile TypeScript to `dist/` |
21+
| `npm test` | Run the test suite (Vitest) |
22+
| `npm run test:watch` | Tests in watch mode |
23+
| `npm run lint` | ESLint |
24+
| `npm run format` | Prettier (write) |
25+
| `npm run format:check` | Prettier (check only) |
26+
27+
## Project layout
28+
29+
```text
30+
src/
31+
├── auth/ # Session & token handling
32+
├── cloud/ # Firestore API client
33+
├── commands/ # CLI commands
34+
├── config/ # Global & project config
35+
├── core/ # Domain logic (app collection, DTOs, diff)
36+
└── lib/ # Shared utilities
37+
tests/
38+
├── auth/ # Auth unit tests (token, session)
39+
├── cloud/ # Firestore client tests
40+
├── config/ # Config tests (project, global)
41+
├── core/ # Core unit tests (appCollector, bundleDiff, buildDocuments)
42+
└── lib/ # Utility tests (spinner)
43+
```
44+
45+
## Advanced: Firebase and backend configuration
46+
47+
Normal installs use Ensemble’s cloud endpoints and a Firebase API key that is **injected at build time** (`npm run build` reads `ENSEMBLE_FIREBASE_API_KEY` via `scripts/inject-env.mjs`). You typically set that only when building or testing the CLI from source.
48+
49+
You can override the defaults at **runtime** (forks, custom backends, local integration tests):
50+
51+
| Variable | Default | Purpose |
52+
| --------------------------- | --------------------------------------- | --------------------------------------------- |
53+
| `ENSEMBLE_FIREBASE_PROJECT` | `ensemble-web-studio` | Firestore / Firebase project ID |
54+
| `ENSEMBLE_AUTH_BASE_URL` | `https://studio.ensembleui.com/sign-in` | Browser sign-in page used by `ensemble login` |
55+
| `ENSEMBLE_FIREBASE_API_KEY` | Injected placeholder in `dist/` | Firebase Web API key (token refresh, etc.) |
56+
57+
If the built-in key is missing or wrong, token refresh may fail with a message to set `ENSEMBLE_FIREBASE_API_KEY`.
58+
59+
## Releases
60+
61+
GitHub Actions bumps the version, tags, creates a GitHub Release, and publishes `@ensembleui/cli` to **GitHub Packages** and \*\*npm`.
62+
63+
**npm: trusted publishing (OIDC)** — Pushes to [registry.npmjs.org](https://www.npmjs.com/) use [trusted publishing](https://docs.npmjs.com/trusted-publishers): short-lived tokens via GitHub OIDC, no `NPM_TOKEN` secret. On npm → **Package****Settings****Trusted publishing**, add **GitHub Actions** with repository `EnsembleUI/ensemble-cli` and workflow filename **`release.yml`** (must match exactly, including `.yml`). `package.json` must keep a correct [`repository`](https://docs.npmjs.com/trusted-publishers) URL for this repo.
64+
65+
**Repository secrets**
66+
67+
| Secret | Purpose |
68+
| --------------------------- | ----------------------------------------- |
69+
| `GH_PACKAGES_TOKEN` | Publish to GitHub Packages |
70+
| `ENSEMBLE_FIREBASE_API_KEY` | Used at build time when compiling the CLI |
71+
72+
To cut a release: GitHub → **Actions****Release (bump version, tag, publish)** → choose `patch`, `minor`, or `major`.
73+
74+
## Security notes (maintainers)
75+
76+
- GitHub tokens used in `.npmrc` for GitHub Packages must not be committed; use least-privilege scopes (`read:packages` for consumers) and rotate if exposed.
77+
- Shell and subprocess usage in the CLI (`npm view`, `npm install -g`, `open`/`start`/`xdg-open`) should stay **static string literals** so user input cannot be injected into shells.

README.md

Lines changed: 14 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,11 @@ CLI for logging in, initializing, and pushing app definitions to the Ensemble cl
44

55
## Installation
66

7-
### From GitHub Packages (recommended)
8-
9-
#### Option 1: One-shot install script
10-
11-
Run this command once (it will prompt for a GitHub token with `read:packages`):
12-
137
```bash
14-
curl -fsSL https://raw.githubusercontent.com/EnsembleUI/ensemble-cli/main/scripts/install-ensemble-cli.sh | bash
8+
npm install -g @ensembleui/cli
159
```
1610

17-
#### Option 2: Manual setup
18-
19-
1. **Configure npm to use GitHub Packages for `@ensembleui`:**
20-
21-
Add this to your `~/.npmrc` (global) or project `.npmrc`:
22-
23-
```bash
24-
@ensembleui:registry=https://npm.pkg.github.com
25-
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
26-
```
27-
28-
Your GitHub token must have at least the `read:packages` scope. Treat this token as a **secret**:
29-
- Do not commit `.npmrc` to source control.
30-
- Prefer the minimum required scopes (typically `read:packages`).
31-
- Rotate the token promptly if you suspect it has been exposed.
32-
33-
2. **Install the CLI globally:**
34-
35-
```bash
36-
npm install -g @ensembleui/cli
37-
```
38-
39-
3. **Use the CLI:**
11+
### Use the CLI
4012

4113
```bash
4214
ensemble login
@@ -50,25 +22,6 @@ ensemble add
5022
ensemble update
5123
```
5224

53-
### Development setup
54-
55-
```bash
56-
npm install
57-
npm run build
58-
npm link # link globally for local development
59-
```
60-
61-
## Releasing (GitHub Packages)
62-
63-
This repo uses GitHub Actions to:
64-
65-
- bump the version in `package.json`
66-
- create a git tag (e.g. `v0.1.0`)
67-
- create a GitHub Release
68-
- publish `@ensembleui/cli` to GitHub Packages
69-
70-
To release a new version, go to GitHub → Actions → run the workflow **Release (bump version, tag, publish)** and choose `patch`, `minor`, or `major`.
71-
7225
## Commands
7326

7427
| Command | Description |
@@ -201,25 +154,26 @@ Without `-y`, both commands refuse to run when not attached to a TTY and exit wi
201154

202155
> **Tip:** In CI, prefer `ensemble push --dry-run` / `ensemble pull --dry-run` in a validation job, and use `-y` only when you are ready to apply changes.
203156

204-
## Environment Variables
157+
## Environment variables
158+
159+
For everyday use you do not need to set anything beyond what is described in [CI/CD](#cicd) (`ENSEMBLE_TOKEN` in automation).
160+
161+
**Optional:**
205162

206163
| Variable | Purpose |
207164
| --------------------------------- | ------------------------------------------------------------------------------------------------------------ |
208-
| `ENSEMBLE_TOKEN` | Token for CI; the CLI uses it instead of global config. Get it with `ensemble token` after `ensemble login`. |
209-
| `ENSEMBLE_FIREBASE_PROJECT` | Firestore project (default: `ensemble-web-studio`) |
210-
| `ENSEMBLE_AUTH_BASE_URL` | Auth sign-in URL (default: `https://studio.ensembleui.com/sign-in`) |
211-
| `ENSEMBLE_FIREBASE_API_KEY` | Firebase API key used by the CLI (injected at build time; can be overridden for custom environments/tests). |
212-
| `ENSEMBLE_VERBOSE` / `VERBOSE` | When set to a truthy value (`1`, `true`, `yes`, `on`), enables verbose mode for commands that support it. |
213-
| `DEBUG` | When set to a truthy value (`1`, `true`, `yes`, `on`), enables debug output (same as passing `--debug`). |
214-
| `CI` / `ENSEMBLE_NO_UPDATE_CHECK` | When set to a truthy value, disables the automatic version check at startup. |
165+
| `ENSEMBLE_VERBOSE` / `VERBOSE` | Truthy values (`1`, `true`, `yes`, `on`) enable verbose mode where supported. |
166+
| `DEBUG` | Same idea as global `--debug` (truthy values as above). |
167+
| `CI` / `ENSEMBLE_NO_UPDATE_CHECK` | Truthy values disable the startup “new version available” check (useful in CI or when you want a quiet run). |
168+
169+
Firebase project, auth URL, and API key are fixed for the published CLI (the API key is injected when the package is built). You only need to think about those when [developing the CLI](CONTRIBUTING.md#advanced-firebase-and-backend-configuration).
215170

216171
## Security considerations
217172

218173
- **Secrets and tokens**
219174
- `ENSEMBLE_TOKEN` (CI token) is a long-lived Firebase refresh token. Store it only in CI secret stores (e.g. GitHub Actions secrets), never in source control or logs.
220175
- The local auth file at `~/.ensemble/cli-config.json` contains ID tokens and refresh tokens for your user account. Anyone who can read this file can act as you in the CLI.
221176
- The CLI now writes `~/.ensemble/cli-config.json` with user-only permissions on POSIX systems (`0700` directory, `0600` file), but you should still treat it as sensitive.
222-
- GitHub tokens used in `.npmrc` (for GitHub Packages) must not be committed or shared; use least-privilege scopes (`read:packages`) and rotate if exposed.
223177
- **Auth and authorization model**
224178
- Authentication is handled via browser sign-in to Ensemble (backed by Firebase). The CLI stores tokens locally and refreshes them via Firebase’s secure token API.
225179
- Authorization is enforced server-side using Firestore security rules and app-level roles (`write`/`owner`). The CLI passes your Firebase ID token as a Bearer token and does not make its own trust decisions beyond handling HTTP responses.
@@ -230,34 +184,9 @@ Without `-y`, both commands refuse to run when not attached to a TTY and exit wi
230184
- All shell commands used by the CLI (`npm view`, `npm install -g`, `open`/`start`/`xdg-open`) are static string literals and must remain so to avoid shell injection.
231185
- Firestore/network debug hooks intentionally avoid logging Authorization headers or raw tokens; custom debug handlers must preserve this invariant.
232186

233-
## Project Structure
234-
235-
```text
236-
src/
237-
├── auth/ # Session & token handling
238-
├── cloud/ # Firestore API client
239-
├── commands/ # CLI commands
240-
├── config/ # Global & project config
241-
├── core/ # Domain logic (app collection, DTOs, diff)
242-
└── lib/ # Shared utilities
243-
tests/
244-
├── auth/ # Auth unit tests (token, session)
245-
├── cloud/ # Firestore client tests
246-
├── config/ # Config tests (project, global)
247-
├── core/ # Core unit tests (appCollector, bundleDiff, buildDocuments)
248-
└── lib/ # Utility tests (spinner)
249-
```
250-
251-
## Development
187+
## Contributing
252188

253-
```bash
254-
npm run dev # Run with ts-node
255-
npm run build # Compile TypeScript
256-
npm run test # Run tests
257-
npm run test:watch # Run tests in watch mode
258-
npm run lint # ESLint
259-
npm run format # Prettier
260-
```
189+
See [CONTRIBUTING.md](CONTRIBUTING.md) for local development, tests, project layout, and the release workflow.
261190

262191
## License
263192

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@
2323
"cli"
2424
],
2525
"files": [
26-
"dist"
26+
"dist",
27+
"README.md"
2728
],
2829
"publishConfig": {
29-
"registry": "https://npm.pkg.github.com"
30+
"access": "public"
31+
},
32+
"repository": {
33+
"type": "git",
34+
"url": "git+https://github.com/EnsembleUI/ensemble-cli.git"
3035
},
3136
"license": "MIT",
3237
"type": "commonjs",

src/commands/update.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@ export async function updateCommand(): Promise<void> {
77
return new Promise((resolve) => {
88
// IMPORTANT: This command string must remain a static literal and MUST NOT
99
// interpolate user-controlled input to avoid shell injection risks.
10-
const child = exec('npm install -g @ensembleui/cli', (error) => {
11-
if (error) {
12-
ui.error('Failed to update @ensembleui/cli automatically.');
13-
ui.note('Please run the following command manually:');
14-
// eslint-disable-next-line no-console
15-
console.log(' npm install -g @ensembleui/cli');
16-
} else {
17-
ui.success('Successfully updated @ensembleui/cli to the latest version.');
10+
const child = exec(
11+
'npm install -g @ensembleui/cli --registry=https://registry.npmjs.org',
12+
(error) => {
13+
if (error) {
14+
ui.error('Failed to update @ensembleui/cli automatically.');
15+
ui.note('Please run the following command manually:');
16+
// eslint-disable-next-line no-console
17+
console.log(' npm install -g @ensembleui/cli --registry=https://registry.npmjs.org');
18+
} else {
19+
ui.success('Successfully updated @ensembleui/cli to the latest version.');
20+
}
21+
resolve();
1822
}
19-
resolve();
20-
});
23+
);
2124

2225
if (child.stdout) {
2326
child.stdout.pipe(process.stdout);

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ function checkForUpdates(): void {
225225
// IMPORTANT: This command string must remain a static literal and MUST NOT
226226
// interpolate user-controlled input to avoid shell injection risks.
227227
exec(
228-
'npm view @ensembleui/cli version --registry=https://npm.pkg.github.com',
228+
'npm view @ensembleui/cli version --registry=https://registry.npmjs.org',
229229
(error, stdout) => {
230230
if (error) {
231231
return;

0 commit comments

Comments
 (0)