Skip to content

Add jss install <name> subcommand (Phase 1 of #464) #478

Description

@melvincarvalho

Phase 1 of the phased install plan (#464 comment). The meaningful core: a single working jss install <name> against the default registry. Once it lands, phases 2–6 (full spec, did:nostr resolution, NIP-98 signed install, curated set, bundles) are mechanical extensions.

Scope

jss install <name> resolves to https://github.com/solid-apps/<name>, clones, and pushes the working tree to <pod>/public/apps/<name>/. Re-runs are idempotent. Each app reports a per-line status.

Acceptance criteria

$ jss start &        # in another terminal / background
$ jss install chrome
Installing 1 app → http://localhost:4443
✓ chrome → http://localhost:4443/public/apps/chrome/
1/1 installed.

$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:4443/public/apps/chrome/index.html
200

$ jss install chrome        # idempotent
✓ chrome → http://localhost:4443/public/apps/chrome/   # or "⊘ skipped" — both fine

Flags

Flag Default Purpose
--pod <url> http://localhost:4443 (post-#477) Target pod URL
--user <name> me Username for token fetch
--password <pw> $JSS_SINGLE_USER_PASSWORD or me Password (env wins over flag default)
--help / -h Subcommand help

Behavior

  1. Auth. POST <pod>/idp/credentials with username=<user>&password=<password>, extract access_token. If pod is in --public mode (no IDP), skip token fetch.
  2. Clone. git clone https://github.com/solid-apps/<name> /tmp/jss-install-<name>-<pid>. No --depth=1 — shallow clones are rejected by git-receive on push.
  3. Push. Dual push to handle both init.defaultBranch conventions:
    git -C /tmp/... -c http.extraHeader="Authorization: Bearer <token>" push <pod>/public/apps/<name> HEAD:main
    git -C /tmp/... -c http.extraHeader="Authorization: Bearer <token>" push <pod>/public/apps/<name> HEAD:gh-pages
    
    Whichever matches server-side HEAD triggers updateInstead and extracts the working tree. The other just creates a stranded ref (harmless).
  4. Skip-on-existing. If the auto-init's path-empty check refuses (e.g. JSS bundles content there), translate the repository not found / 404 from the push into a friendly ⊘ <name>: skipped (path already in use — bundled or manually placed).
  5. Cleanup. rm -rf /tmp/jss-install-<name>-<pid> always, even on failure.
  6. Output. Per-app ✓ <name> → <pod>/public/apps/<name>/, ⊘ <name>: skipped, or ✗ <name>: <reason>. Trailing N/M installed. summary.
  7. Exit code. 0 if all installs succeeded or skipped; non-zero if any failed.

Validation

<name> must match /^[a-z0-9][a-z0-9_.-]*$/i. Reject .., slashes (Phase 2 introduces them with a different shape), shell metacharacters.

Implementation notes

  • Reference implementation: jspod's runInstall in bin/index.js — ~120 LoC. Port shape is direct; just drop the jspod-specific banner integration and the bundled-pilot heuristic (JSS doesn't bundle apps).
  • Use spawnSync for git operations (already used in src/handlers/git.js's auto-init path).
  • New file: src/cli/install.js exporting an async function the CLI dispatches to.
  • CLI dispatch: extend bin/jss.js's subcommand handling.
  • No new runtime dependencies needed.

Test plan

  • jss install chrome against a fresh pod → working tree extracts, index.html returns 200
  • Re-running the same install → no error, reports installed or skipped
  • jss install nonexistent-name-that-doesnt-resolve✗ <name>: clone failed, exit non-zero
  • jss install chrome --pod http://other:5444 against a remote pod with the same auth flow
  • --password over env: JSS_SINGLE_USER_PASSWORD=foo jss install chrome uses the env value
  • jss install chrome --user wrong --password wrong✗ Could not authenticate against <pod>, exit non-zero
  • jss install --help shows the subcommand help

Depends on

  • #477 — port default fallback (--pod default should be 4443, not 3000)

Not in scope (later phases)

  • <org>/<repo> shorthand (Phase 2)
  • Full URLs, #<ref> pinning, =<name> rename (Phase 2)
  • --did resolution (Phase 3)
  • --nostr-privkey NIP-98 auth (Phase 4)
  • No-arg curated set (Phase 5)
  • --bundle (Phase 6)

Parent

#464 (phased plan in the comment thread)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions