# Make your own App

> Copy Hello into a separate App, commit it, and open it alongside Hello.

Start with [the local trial](/oyster-quickstart/) and [edit Hello](/build/).
Keep Oyster running in its terminal. In a second terminal, start from the folder
containing `my-oyster`. You need Node.js and Git, but no account or payment.
These commands use the Oyster package already installed in your trial project.

## Create My App

Copy the working bundle, including its readiness script and styles, into a new
Git repository. The block stops if any command fails, including when `my-app`
already exists. To keep working on an existing App, use **Open and change it**
below; choose another folder for a new App.

```sh
cd my-oyster && (
set -e
mkdir my-app
cp -R hello-app/bundle my-app/bundle
node --input-type=module <<'JS'
import { writeFileSync } from "node:fs";
writeFileSync("my-app/package.json", JSON.stringify({
  name: "my-app", private: true,
  fungi: { app: { displayName: "My App", icon: "fungi", capabilities: [] } }
}, null, 2) + "\n");
JS
cat > my-app/bundle/index.html <<'HTML'
<!doctype html>
<html lang="en" data-theme="latte">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My App</title>
  <link rel="stylesheet" href="./lesson.css">
</head>
<body>
  <main>
    <h1>This is my own App</h1>
    <p>Edit my-app/bundle/index.html to make this page yours. Keep the main.js script: it tells Oyster this App is ready.</p>
    <p>From my-oyster, commit your edit and install it:</p>
    <pre><code>git -C my-app -c user.name=Me -c user.email=me@example.invalid commit -am "Update My App"
node install-my-app.mjs</code></pre>
    <p>Close this window, refresh the desktop, and open My App again. Oyster can stay running.</p>
  </main>
  <script type="module" src="./main.js"></script>
</body>
</html>
HTML
git -C my-app init -q
git -C my-app add package.json bundle
git -C my-app -c user.name=Me -c user.email=me@example.invalid commit -qm "Create My App"
)
```

Expected: `my-oyster/my-app` has its own first commit. Hello is still a separate
App. Keep `bundle/main.js` and its script tag in the page: they tell the desktop
that the App is ready.

## Pack and install

Stay in `my-oyster`. Create this script once, then run it again after each
committed edit. If you started the trial on a custom port, first set
`export OYSTER_URL=http://127.0.0.1:YOUR_PORT` to its printed address.

```sh
cat > install-my-app.mjs <<'JS'
import { execFileSync } from "node:child_process";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { packRelease } from "@fungi.computer/oyster/pack";

const source = resolve("my-app");
const commit = execFileSync("git", ["-C", source, "rev-parse", "HEAD"], {
  encoding: "utf8",
}).trim();
const out = await mkdtemp(join(tmpdir(), "my-app-release-"));
try {
  console.log("Packing My App from " + commit);
  await packRelease({
    appId: "app_myapp",
    sourceDirectory: source,
    sourceRevision: `git:${commit}`,
    recipe: {
      kind: "bundled-html", entry: "bundle/index.html",
      assets: {
        maxManifestBytes: 65_536, maxObjects: 16,
        maxObjectBytes: 1_048_576, maxTotalBytes: 4_194_304,
      },
      assetBase: "./", backend: null, maxOutputBytes: 4_194_304,
    },
    outDirectory: join(out, "release"),
  });
  execFileSync("npx", ["oyster", "install", join(out, "release"), "--installation-id", "my-app"], {
    stdio: "inherit",
    env: {
      ...process.env,
      OYSTER_URL: process.env.OYSTER_URL ?? "http://127.0.0.1:8787",
      OYSTER_OWNER_KEY: (await readFile(".oyster-owner", "utf8")).trim(),
    },
  });
} catch (error) {
  console.error(error instanceof Error ? error.message : String(error));
  console.error("Check the committed bundle: git -C my-app ls-tree -r --name-only HEAD");
  console.error("Restore any missing bundle/index.html or main.js, commit, then run node install-my-app.mjs again.");
  console.error("For connection or sign-in errors, check the trial terminal and .oyster-owner in this project.");
  process.exitCode = 1;
} finally {
  await rm(out, { recursive: true, force: true });
}
JS
node install-my-app.mjs
```

Expected: `Packing My App`, Oyster’s installation progress, then a JSON result
containing `"installationId":"my-app"` and an `Open` link. The script uses
`npx oyster install` for both first installs and updates; exit code 0 means it
succeeded. This packs files that are already built; it does not run a compiler
or `npm run build`.

## Open and change it

Return to the signed-in desktop, refresh the desktop to see the new
installation, and open **My App**. Check that **This is my own App** is visible
with no loading or error overlay. **Hello** should still open too. An install
success alone does not prove the App is visible.

Edit `my-app/bundle/index.html`, keeping its readiness script, then:

```sh
git -C my-app -c user.name=Me -c user.email=me@example.invalid commit -am "Update My App"
node install-my-app.mjs
```

Close My App's window, refresh the desktop, and open My App again to see the
installed Release. Oyster can stay running throughout this loop. Uncommitted
changes do not appear.

## If it fails

- **Old text:** run `git -C my-app status --short`. Save and commit the edited
  files, rerun `node install-my-app.mjs`, then reopen the App.
- **Window waits or times out:** check the browser console for a script error.
  Restore the starter readiness script with
  `cp hello-app/bundle/main.js my-app/bundle/main.js`, and restore its script
  tag in `bundle/index.html` if missing. Commit the repair, rerun the install
  script, and reopen My App.
- **JavaScript syntax error:** fix the file and line reported by the browser
  console. `node --check my-app/bundle/main.js` can check JavaScript syntax
  before committing. Packing static files does not execute or validate them.
- **Packing fails:** read the terminal error. Check that `bundle/index.html`
  exists in the committed tree (`git -C my-app ls-tree -r --name-only HEAD`).
  Restore a missing entry, commit it, and rerun the script. No new installation
  is requested until packing succeeds; your previous Release stays installed.
- **Connection refused:** keep the trial terminal running and check `OYSTER_URL`
  against the address it prints.
- **401:** run this script from the same `my-oyster` project as the running
  trial so it reads that project's `.oyster-owner`. Do not share that key.

For a typed backend and a build pipeline, continue with
[Build Apps](/build/#add-a-backend).
