@bardioc/create-bardioc-app 0.4.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 +5 -0
- package/README.md +105 -0
- package/bin/create.mjs +76 -0
- package/package.json +45 -0
- package/src/scaffold.d.ts +50 -0
- package/src/scaffold.js +379 -0
- package/templates/_base/.changeset/README.md +17 -0
- package/templates/_base/.changeset/config.json +12 -0
- package/templates/_base/.claude/commands/changeset-app.md +104 -0
- package/templates/_base/.claude/commands/refresh-bundle.md +101 -0
- package/templates/_base/README.md +89 -0
- package/templates/_base/public/app-manifest.json +10 -0
- package/templates/_base/scripts/stamp-manifest.mjs +17 -0
- package/templates/_opt-link/_npmrc +2 -0
- package/templates/_opt-link/pnpm-workspace.yaml +3 -0
- package/templates/_opt-link/scripts/link-source.mjs +188 -0
- package/templates/_opt-pipeline/bitbucket-pipelines.yml +100 -0
- package/templates/angular/.env.example +5 -0
- package/templates/angular/_gitignore +7 -0
- package/templates/angular/angular.json +64 -0
- package/templates/angular/package.json +49 -0
- package/templates/angular/postcss.config.mjs +5 -0
- package/templates/angular/public/icon.svg +1 -0
- package/templates/angular/public/runtime-env.js +1 -0
- package/templates/angular/scripts/dev-auth-proxy.mjs +92 -0
- package/templates/angular/scripts/sync-runtime-env.mjs +89 -0
- package/templates/angular/src/app/app.component.ts +181 -0
- package/templates/angular/src/app/bardioc-bridge.ts +5 -0
- package/templates/angular/src/app/bardioc.token.ts +4 -0
- package/templates/angular/src/index.html +15 -0
- package/templates/angular/src/main.ts +82 -0
- package/templates/angular/src/runtime-env.ts +17 -0
- package/templates/angular/src/styles.css +258 -0
- package/templates/angular/src/vite-env.d.ts +10 -0
- package/templates/angular/tsconfig.json +27 -0
- package/templates/manifest.json +13 -0
- package/templates/nextjs/.env.example +8 -0
- package/templates/nextjs/_gitignore +7 -0
- package/templates/nextjs/app/globals.css +75 -0
- package/templates/nextjs/app/layout.tsx +17 -0
- package/templates/nextjs/app/page.tsx +222 -0
- package/templates/nextjs/app/proxy/route.ts +85 -0
- package/templates/nextjs/global.d.ts +1 -0
- package/templates/nextjs/next-env.d.ts +5 -0
- package/templates/nextjs/next.config.ts +21 -0
- package/templates/nextjs/package.json +32 -0
- package/templates/nextjs/postcss.config.mjs +5 -0
- package/templates/nextjs/public/icon.svg +1 -0
- package/templates/nextjs/tailwind.config.ts +10 -0
- package/templates/nextjs/tsconfig.json +27 -0
- package/templates/preact/.env.example +13 -0
- package/templates/preact/_gitignore +9 -0
- package/templates/preact/index.html +13 -0
- package/templates/preact/package.json +32 -0
- package/templates/preact/public/icon.svg +1 -0
- package/templates/preact/src/App.tsx +139 -0
- package/templates/preact/src/index.css +76 -0
- package/templates/preact/src/main.tsx +46 -0
- package/templates/preact/src/vite-env.d.ts +11 -0
- package/templates/preact/tsconfig.json +19 -0
- package/templates/preact/vite.config.ts +17 -0
- package/templates/solid/.env.example +13 -0
- package/templates/solid/_gitignore +9 -0
- package/templates/solid/index.html +13 -0
- package/templates/solid/package.json +32 -0
- package/templates/solid/public/icon.svg +1 -0
- package/templates/solid/src/App.tsx +150 -0
- package/templates/solid/src/bardioc-sdk.tsx +33 -0
- package/templates/solid/src/index.css +76 -0
- package/templates/solid/src/main.tsx +50 -0
- package/templates/solid/src/vite-env.d.ts +11 -0
- package/templates/solid/tsconfig.json +20 -0
- package/templates/solid/vite.config.ts +17 -0
- package/templates/svelte/.env.example +5 -0
- package/templates/svelte/_gitignore +6 -0
- package/templates/svelte/index.html +13 -0
- package/templates/svelte/package.json +32 -0
- package/templates/svelte/public/icon.svg +1 -0
- package/templates/svelte/src/App.svelte +135 -0
- package/templates/svelte/src/index.css +76 -0
- package/templates/svelte/src/main.ts +42 -0
- package/templates/svelte/src/vite-env.d.ts +11 -0
- package/templates/svelte/tsconfig.json +13 -0
- package/templates/svelte/vite.config.ts +17 -0
- package/templates/vite/.env.example +14 -0
- package/templates/vite/CLAUDE.md +114 -0
- package/templates/vite/_gitignore +9 -0
- package/templates/vite/index.html +13 -0
- package/templates/vite/package.json +34 -0
- package/templates/vite/public/icon.svg +1 -0
- package/templates/vite/src/App.tsx +141 -0
- package/templates/vite/src/index.css +76 -0
- package/templates/vite/src/main.tsx +44 -0
- package/templates/vite/src/vite-env.d.ts +11 -0
- package/templates/vite/tsconfig.json +18 -0
- package/templates/vite/vite.config.ts +17 -0
- package/templates/vue/.env.example +5 -0
- package/templates/vue/_gitignore +6 -0
- package/templates/vue/index.html +13 -0
- package/templates/vue/package.json +32 -0
- package/templates/vue/public/icon.svg +1 -0
- package/templates/vue/src/App.vue +127 -0
- package/templates/vue/src/bardioc-sdk.ts +23 -0
- package/templates/vue/src/index.css +76 -0
- package/templates/vue/src/main.ts +43 -0
- package/templates/vue/src/vite-env.d.ts +17 -0
- package/templates/vue/tsconfig.json +26 -0
- package/templates/vue/vite.config.ts +17 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: changeset-app
|
|
3
|
+
description: Write a changeset for this app repo's single published package, pick the semver bump (default patch, major on breaking change), and apply the bump locally — keeping package.json's version in step with the release.
|
|
4
|
+
argument-hint: "[optional: summary, and/or 'minor'/'major' to override the bump]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /changeset-app — Author + bump locally
|
|
8
|
+
|
|
9
|
+
This is a **single-package hosted-app repo**: it publishes exactly one package (read its name from
|
|
10
|
+
this repo's `package.json` `name` field). This skill writes the `.changeset/*.md`, picks the **bump**,
|
|
11
|
+
and **applies it** (`pnpm release:version`), so `package.json`'s `version` always reflects the
|
|
12
|
+
release. The result is committed with your PR; the pipeline only publishes.
|
|
13
|
+
|
|
14
|
+
> Named `changeset-app` (not `changeset`) on purpose: `bardioc-desktop-frontend` ships its own
|
|
15
|
+
> `/changeset`, and project commands don't namespace across repos — a shared name would collide
|
|
16
|
+
> when an app repo and the frontend are open in one workspace. Caveat: if you open **two app
|
|
17
|
+
> repos** at once they both expose `changeset-app`; in practice you work on one app at a time
|
|
18
|
+
> alongside the frontend, so this is fine.
|
|
19
|
+
|
|
20
|
+
## Step 0 — Identify the package
|
|
21
|
+
|
|
22
|
+
Read the published package name from this repo's `package.json` `name` field. This is the **only**
|
|
23
|
+
app-specific value — use it verbatim in the changeset frontmatter (Step 2). Everything else below is
|
|
24
|
+
identical across app repos.
|
|
25
|
+
|
|
26
|
+
## Arguments
|
|
27
|
+
|
|
28
|
+
The user invoked this with: `$ARGUMENTS`
|
|
29
|
+
|
|
30
|
+
- If it contains `major` / `minor` / `patch` → use that as the **bump override**.
|
|
31
|
+
- Otherwise treat it as the **one-line summary**. If empty, draft the summary from the diff.
|
|
32
|
+
|
|
33
|
+
## Step 1 — Decide the bump
|
|
34
|
+
|
|
35
|
+
**Default: `patch`.** Then escalate based on what changed:
|
|
36
|
+
|
|
37
|
+
- **`major`** — a **breaking** change for consumers / the host integration. Signals:
|
|
38
|
+
- a public export, prop, type, or `bridge.*` contract that **existed is removed or renamed**,
|
|
39
|
+
- behavior changed in a way that breaks an embedding host or a saved session,
|
|
40
|
+
- a required new input where there wasn't one.
|
|
41
|
+
- **`minor`** — a notable backward-compatible **addition** (new feature/export). Optional;
|
|
42
|
+
purely additive props may stay `patch`.
|
|
43
|
+
- **`patch`** — fixes, internal refactors, additive optional props. **This is the default.**
|
|
44
|
+
|
|
45
|
+
`$ARGUMENTS` override wins. When genuinely unsure between patch and major, **choose major**
|
|
46
|
+
(under-bumping silently breaks consumers) and say why in one line.
|
|
47
|
+
|
|
48
|
+
> 0.x caret trap: on `^0.x.y`, a **minor** bump (`0.2→0.3`) breaks a consumer's `^` range and
|
|
49
|
+
> forces a manual cross-repo update, while a **patch** (`0.0.3→0.0.4`) is picked up
|
|
50
|
+
> automatically. Prefer `patch` for backward-compatible changes while pre-1.0.
|
|
51
|
+
|
|
52
|
+
### Bumped a `@bardioc/*` dependency that the app needs? Bump the app too.
|
|
53
|
+
|
|
54
|
+
If this change raises a `@bardioc/*` dependency in `package.json` (e.g. `@bardioc/ui` ↑ because
|
|
55
|
+
the app now uses a new component/prop/type from it), that is a **release-relevant change**:
|
|
56
|
+
write a changeset so the app's **own** version bumps as well. The published app version must
|
|
57
|
+
move whenever its required dependency set moves — otherwise the package.json version no longer
|
|
58
|
+
identifies a distinct release.
|
|
59
|
+
|
|
60
|
+
## Step 2 — Write the changeset, then apply the bump
|
|
61
|
+
|
|
62
|
+
Write `.changeset/<short-kebab-slug>.md` using **this repo's** package name (from Step 0):
|
|
63
|
+
|
|
64
|
+
```markdown
|
|
65
|
+
---
|
|
66
|
+
'<package-name-from-package.json>': patch
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
<one-line consumer-facing summary>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then apply the version bump locally:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pnpm release:version # = changeset version && pnpm install --lockfile-only
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This rewrites `package.json`'s `version`, updates `CHANGELOG.md`, syncs the lockfile, and
|
|
79
|
+
consumes the changeset file.
|
|
80
|
+
|
|
81
|
+
## Step 3 — Report
|
|
82
|
+
|
|
83
|
+
Tell the user: from → to version, the bump level + one-line reasoning. Leave the changes
|
|
84
|
+
**uncommitted** — they ride along with the PR commit (don't commit unless asked). Then remind
|
|
85
|
+
them to run **`/refresh-bundle`** so the new version reaches the shipped artifact (see the
|
|
86
|
+
manifest note below) — a bump alone does not update the zip.
|
|
87
|
+
|
|
88
|
+
## Notes
|
|
89
|
+
|
|
90
|
+
- **App-store version is auto-stamped — never hand-edit the manifest.** This app stamps its
|
|
91
|
+
`public/app-manifest.json` version from `package.json` at bundle time (via
|
|
92
|
+
`scripts/stamp-manifest.mjs`, wired into `pnpm bundle`/`pnpm build`), so the WebOS app store reads
|
|
93
|
+
that stamped version, and this skill's `package.json` bump is the _only_ edit needed. It reaches
|
|
94
|
+
the store only after a **re-bundle**: run **`/refresh-bundle`** after bumping (before merging) to
|
|
95
|
+
rebuild the zip and swap it into the host's `apps/webos-host/public/_apps/`. The committed
|
|
96
|
+
`app-manifest.json` `version` is just a seed — the stamp overwrites it in the build output, so
|
|
97
|
+
don't sync it by hand.
|
|
98
|
+
- **Bump before merging to `dev`** so the release carries a fresh version. There is **no
|
|
99
|
+
release gate**: if you forget, `pnpm changeset publish` just no-ops (green, "No unpublished
|
|
100
|
+
projects") rather than failing — the merge succeeds but publishes nothing until you bump. (While
|
|
101
|
+
the package is private, publish always no-ops regardless.)
|
|
102
|
+
- Bumping locally is why CI needs **no git-write token** — the version is already in the PR;
|
|
103
|
+
the pipeline only runs `pnpm changeset publish` (which publishes only if the version isn't on
|
|
104
|
+
Nexus yet and the package is publishable).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refresh-bundle
|
|
3
|
+
description: Rebuild this app's zip and replace the committed copy in the WebOS host's apps/webos-host/public/_apps/. Removes the stale zip first, runs `pnpm bundle`, then copies the fresh zip over.
|
|
4
|
+
argument-hint: '[absolute path to the bardioc-desktop-frontend (WebOS host) checkout]'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /refresh-bundle — Rebuild & Replace this Hosted App Bundle
|
|
8
|
+
|
|
9
|
+
Re-bundle **this** standalone hosted app and swap the fresh zip into the WebOS host's
|
|
10
|
+
`public/_apps/` so the running shell serves the latest build.
|
|
11
|
+
|
|
12
|
+
The flow, end to end:
|
|
13
|
+
|
|
14
|
+
1. **Remove** the stale zip in this app repo (forces a clean rebundle, never copies a stale artifact).
|
|
15
|
+
2. **Bundle** via this repo's `pnpm bundle` script (`<build> && node scripts/stamp-manifest.mjs && zip → <app>.zip`).
|
|
16
|
+
3. **Replace** `<host>/apps/webos-host/public/_apps/<app>.zip` with the freshly built one.
|
|
17
|
+
|
|
18
|
+
## Arguments
|
|
19
|
+
|
|
20
|
+
The user invoked this command with: `$ARGUMENTS`
|
|
21
|
+
|
|
22
|
+
`REPO` below = **this** app repo (the directory this skill runs in — use `pwd`).
|
|
23
|
+
`HOST` = the WebOS host checkout (`bardioc-desktop-frontend`), resolved from `$ARGUMENTS`:
|
|
24
|
+
|
|
25
|
+
- **An absolute path** that exists → use it as `HOST`.
|
|
26
|
+
- **Empty** → **ask the user for the host repo path.** Do not guess — the app does not know where
|
|
27
|
+
the host is checked out.
|
|
28
|
+
|
|
29
|
+
## Step 1 — Resolve the zip filename
|
|
30
|
+
|
|
31
|
+
The zip name is **not** hardcoded — derive it from this repo so the skill works for any app:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
test -f "$REPO/package.json" || { echo "No package.json at $REPO"; exit 1; }
|
|
35
|
+
grep -E '"bundle"' "$REPO/package.json"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Parse the `*.zip` filename out of the `bundle` script and take its **basename** — strip any leading
|
|
39
|
+
directory / `../` segments so you get the bare `<slug>.zip` the bundle writes to the repo root.
|
|
40
|
+
(Most templates zip from `dist/` via `zip -r ../<slug>.zip`, but Angular zips from
|
|
41
|
+
`dist/<slug>/browser/` via `zip -r ../../../<slug>.zip` — the basename is `<slug>.zip` in every
|
|
42
|
+
case.) Call it `ZIP`.
|
|
43
|
+
|
|
44
|
+
Sanity-check the target exists so we know we're replacing, not silently adding:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
ls -l "$HOST/apps/webos-host/public/_apps/$ZIP"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- If it **exists** → this is a replace (the normal case). Note its current byte size.
|
|
51
|
+
- If it **does NOT exist** → this would be an _add_, not a replace. Stop and confirm with the user
|
|
52
|
+
that adding a new entry to the host is intended before continuing.
|
|
53
|
+
|
|
54
|
+
## Step 2 — Remove the stale zip in this repo
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
rm -f "$REPO/$ZIP"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Step 3 — Bundle in this repo
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cd "$REPO" && pnpm bundle
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Then verify the artifact was actually produced:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
test -f "$REPO/$ZIP" && ls -l "$REPO/$ZIP" || { echo "Bundle did not produce $ZIP"; exit 1; }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If `pnpm bundle` fails (build errors), **stop and report the real failure** — do not copy anything.
|
|
73
|
+
You may fix an obvious issue and retry once; on a second failure, report it truthfully and ask the
|
|
74
|
+
user how to proceed.
|
|
75
|
+
|
|
76
|
+
## Step 4 — Replace the bundle in the host's public/\_apps
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
cp -f "$REPO/$ZIP" "$HOST/apps/webos-host/public/_apps/$ZIP"
|
|
80
|
+
ls -l "$HOST/apps/webos-host/public/_apps/$ZIP"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Step 5 — Wrap up
|
|
84
|
+
|
|
85
|
+
Report in one block:
|
|
86
|
+
|
|
87
|
+
- App + the resolved `ZIP` filename, and the `HOST` path used.
|
|
88
|
+
- Old vs new byte size of `public/_apps/$ZIP` (so the user sees the swap actually happened).
|
|
89
|
+
- `git -C "$HOST" status --short apps/webos-host/public/_apps/` to confirm the tracked zip is now
|
|
90
|
+
modified, ready to commit **in the host repo**.
|
|
91
|
+
|
|
92
|
+
Do **not** commit unless the user asks.
|
|
93
|
+
|
|
94
|
+
## Notes
|
|
95
|
+
|
|
96
|
+
- This app is decoupled from the host's workspace (its own `pnpm-lock.yaml`). Always run
|
|
97
|
+
`pnpm bundle` from inside `REPO`, never from the host root.
|
|
98
|
+
- Removing the repo zip first (Step 2) is deliberate: it guarantees the copy in Step 4 is a
|
|
99
|
+
brand-new build, never a leftover artifact.
|
|
100
|
+
- The `public/_apps/<app>.zip` is git-tracked in the host repo, so the previous bundle is always
|
|
101
|
+
recoverable via git if a refresh goes wrong.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# {{DISPLAY_NAME}}
|
|
2
|
+
|
|
3
|
+
A Bardioc hosted app, scaffolded with `@bardioc/create-bardioc-app`. Runs standalone (not as a
|
|
4
|
+
workspace member of `bardioc-desktop-frontend`) so it can be bundled and shipped as a hosted Bardioc
|
|
5
|
+
app.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Develop
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm dev # http://localhost:{{PORT}}
|
|
17
|
+
pnpm dev:live # public tunnel (BARDIOC_TUNNEL=1)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Run `bardioc login` once; `pnpm dev` then mints and refreshes the standalone dev session
|
|
21
|
+
automatically. See `.env.example` for the required variables.
|
|
22
|
+
|
|
23
|
+
## Develop against `@bardioc/*` source (optional)
|
|
24
|
+
|
|
25
|
+
> Available only if you scaffolded with `--with-link` (adds `scripts/link-source.mjs`,
|
|
26
|
+
> `pnpm-workspace.yaml`, and `.npmrc`). Otherwise this section does not apply — every `@bardioc/*`
|
|
27
|
+
> dep resolves from Nexus.
|
|
28
|
+
|
|
29
|
+
By default, every `@bardioc/*` dependency comes from Nexus. To develop against a local source
|
|
30
|
+
checkout instead — for example, to edit `@bardioc/ui` and see HMR in this app — symlink the source
|
|
31
|
+
repo into `.linked/`:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm link:source <path-to-source-repo> # e.g. ../bardioc-desktop-frontend
|
|
35
|
+
pnpm install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The link name is derived from the path's basename, so this creates
|
|
39
|
+
`.linked/<name> → <path>` (`.linked/` is gitignored). `pnpm-workspace.yaml`'s `./.linked/*/packages/*`
|
|
40
|
+
glob picks it up; the dev server serves the linked source and HMR fires on edits. If the linked
|
|
41
|
+
source uses pnpm catalogs, `link:source` copies its `catalog:` / `catalogs:` blocks into a managed
|
|
42
|
+
block at the bottom of `pnpm-workspace.yaml` (don't hand-edit it — re-run `link:source` to refresh).
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pnpm link:source ../frontend-experiment --as bardioc-desktop-frontend # custom name
|
|
46
|
+
pnpm unlink:source <name> # remove a link
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### CI / Docker
|
|
50
|
+
|
|
51
|
+
No `.linked/` directory exists, so the workspace glob matches nothing and `@bardioc/*` always
|
|
52
|
+
resolves from Nexus. No overlay or CI-only workspace file needed.
|
|
53
|
+
|
|
54
|
+
## Build & Bundle
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm build # type-check + build (+ stamp the manifest version) → output dir
|
|
58
|
+
pnpm bundle # build + zip → {{APP_NAME}}.zip
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`scripts/stamp-manifest.mjs` folds `package.json`'s `version` into the built `app-manifest.json` so
|
|
62
|
+
the WebOS app store shows the real release version. `{{APP_NAME}}.zip` is the deliverable uploaded
|
|
63
|
+
to the Bardioc host as a hosted app. To swap a fresh build into a local WebOS host checkout, run the
|
|
64
|
+
**`/refresh-bundle`** skill.
|
|
65
|
+
|
|
66
|
+
## Release & Publishing
|
|
67
|
+
|
|
68
|
+
This app is versioned with [Changesets](https://github.com/changesets/changesets). **The version
|
|
69
|
+
bump is done locally** (so CI needs no git-write token), and there is **no release gate** — a
|
|
70
|
+
forgotten bump just publishes nothing rather than failing the deploy.
|
|
71
|
+
|
|
72
|
+
> The CI pipeline below (PR checks + the publish step) is present only if you scaffolded with
|
|
73
|
+
> `--with-pipeline`. Without it, version locally with the steps below and bundle/ship by hand.
|
|
74
|
+
|
|
75
|
+
**To cut a release:**
|
|
76
|
+
|
|
77
|
+
1. On your feature branch, run the **`/changeset-app`** skill (or `pnpm changeset`) — it picks the
|
|
78
|
+
semver bump, writes `.changeset/*.md`, and applies it locally (`pnpm release:version`). Commit the
|
|
79
|
+
result with your PR. Then run **`/refresh-bundle`** so the new version reaches the shipped zip.
|
|
80
|
+
2. Open a PR. The PR pipeline runs `check-types`, `build`, and `audit` in parallel. No publish.
|
|
81
|
+
3. Merge to `dev`. CI runs again, then the publish step runs.
|
|
82
|
+
|
|
83
|
+
> **Publishing is dormant by default.** This app ships `"private": true` with an unscoped name, so
|
|
84
|
+
> `pnpm changeset publish` cleanly no-ops. To publish to Nexus, scope the package name
|
|
85
|
+
> (e.g. `@bardioc/{{APP_NAME}}`), set `"private": false`, and add a `publishConfig.registry` — then
|
|
86
|
+
> the pipeline's publish step ships the built output (set `"files": ["<output-dir>"]` accordingly).
|
|
87
|
+
>
|
|
88
|
+
> **CI prerequisite** (set as a Bitbucket repo/workspace variable, not in the repo):
|
|
89
|
+
> `NEXUS_REPO_USER` / `NEXUS_REPO_PASS` — an account with `npm-releases` deploy rights.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Folds package.json's version into the built app-manifest.json so the WebOS app store (name +
|
|
2
|
+
// description only) shows the release version. Output dir comes from argv (defaults to dist). Idempotent.
|
|
3
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
const root = new URL('..', import.meta.url);
|
|
6
|
+
const outDir = process.argv[2] ?? 'dist';
|
|
7
|
+
const { version } = JSON.parse(readFileSync(new URL('package.json', root), 'utf8'));
|
|
8
|
+
|
|
9
|
+
const url = new URL(`${outDir}/app-manifest.json`, root);
|
|
10
|
+
const manifest = JSON.parse(readFileSync(url, 'utf8'));
|
|
11
|
+
const base = (manifest.description ?? '').replace(/\s*\(v[^)]*\)\s*$/, '').trim();
|
|
12
|
+
|
|
13
|
+
manifest.version = version;
|
|
14
|
+
manifest.description = base ? `${base} (v${version})` : `v${version}`;
|
|
15
|
+
|
|
16
|
+
writeFileSync(url, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
17
|
+
console.log(`[stamp-manifest] ${version} → ${outDir}/app-manifest.json`);
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Symlinks a local @bardioc/* source checkout into .linked/ and syncs its pnpm catalogs into
|
|
3
|
+
// pnpm-workspace.yaml. Without a link the workspace glob matches nothing and deps resolve from Nexus.
|
|
4
|
+
// node link-source.mjs <path> [--as <name>] link (name defaults to basename)
|
|
5
|
+
// node link-source.mjs --remove <name> remove a link
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
existsSync,
|
|
9
|
+
lstatSync,
|
|
10
|
+
mkdirSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
readFileSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
symlinkSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from 'node:fs';
|
|
17
|
+
import { basename, resolve } from 'node:path';
|
|
18
|
+
|
|
19
|
+
const LINK_DIR = '.linked';
|
|
20
|
+
const WORKSPACE_FILE = 'pnpm-workspace.yaml';
|
|
21
|
+
const MARKER_BEGIN = '# BEGIN linked-catalogs (managed by link:source — do not edit)';
|
|
22
|
+
const MARKER_END = '# END linked-catalogs';
|
|
23
|
+
|
|
24
|
+
const argv = process.argv.slice(2);
|
|
25
|
+
|
|
26
|
+
if (argv[0] === '--remove') {
|
|
27
|
+
const name = argv[1];
|
|
28
|
+
if (!name) fail('Usage: pnpm unlink:source <name>');
|
|
29
|
+
const link = `${LINK_DIR}/${name}`;
|
|
30
|
+
if (!exists(link)) fail(`No link at ${link}`);
|
|
31
|
+
rmSync(link);
|
|
32
|
+
console.log(`✓ unlinked ${name}`);
|
|
33
|
+
syncCatalogs();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let target = null;
|
|
38
|
+
let customName = null;
|
|
39
|
+
for (let i = 0; i < argv.length; i++) {
|
|
40
|
+
if (argv[i] === '--as') customName = argv[++i];
|
|
41
|
+
else if (!argv[i].startsWith('--')) target = argv[i];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!target) fail('Usage: pnpm link:source <path-to-source-repo> [--as <name>]');
|
|
45
|
+
|
|
46
|
+
const abs = resolve(target);
|
|
47
|
+
if (!existsSync(`${abs}/packages`)) fail(`No packages/ at ${abs} — wrong path?`);
|
|
48
|
+
|
|
49
|
+
const name = customName ?? basename(abs);
|
|
50
|
+
if (!name) fail('Could not derive name from path — pass --as <name>');
|
|
51
|
+
|
|
52
|
+
mkdirSync(LINK_DIR, { recursive: true });
|
|
53
|
+
const link = `${LINK_DIR}/${name}`;
|
|
54
|
+
if (exists(link)) rmSync(link);
|
|
55
|
+
symlinkSync(abs, link, 'junction');
|
|
56
|
+
console.log(`✓ linked ${name} → ${abs}`);
|
|
57
|
+
|
|
58
|
+
syncCatalogs();
|
|
59
|
+
|
|
60
|
+
console.log('Next: pnpm install');
|
|
61
|
+
|
|
62
|
+
// ── helpers ──────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
function exists(path) {
|
|
65
|
+
try {
|
|
66
|
+
lstatSync(path);
|
|
67
|
+
return true;
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function fail(msg) {
|
|
74
|
+
console.error(msg);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function syncCatalogs() {
|
|
79
|
+
if (!existsSync(WORKSPACE_FILE)) return;
|
|
80
|
+
const sources = collectCatalogSources();
|
|
81
|
+
assertSingleSource(sources);
|
|
82
|
+
const marker = buildMarkerBlock(sources);
|
|
83
|
+
rewriteWorkspaceMarker(marker, sources);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function collectCatalogSources() {
|
|
87
|
+
if (!existsSync(LINK_DIR)) return [];
|
|
88
|
+
const sources = [];
|
|
89
|
+
for (const linkName of readdirSync(LINK_DIR).sort()) {
|
|
90
|
+
const linkedFile = `${LINK_DIR}/${linkName}/${WORKSPACE_FILE}`;
|
|
91
|
+
if (!existsSync(linkedFile)) continue;
|
|
92
|
+
const extracted = extractCatalogs(readFileSync(linkedFile, 'utf-8'));
|
|
93
|
+
if (extracted) sources.push({ name: linkName, content: extracted });
|
|
94
|
+
}
|
|
95
|
+
return sources;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function assertSingleSource(sources) {
|
|
99
|
+
if (sources.length > 1) {
|
|
100
|
+
const names = sources.map(s => s.name).join(', ');
|
|
101
|
+
fail(
|
|
102
|
+
`Multiple linked sources define catalogs (${names}). ` +
|
|
103
|
+
`pnpm requires a single catalog block — merge manually or unlink all but one.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildMarkerBlock(sources) {
|
|
109
|
+
if (sources.length !== 1) return '';
|
|
110
|
+
const { name: sourceName, content } = sources[0];
|
|
111
|
+
return `${MARKER_BEGIN}\n# from .linked/${sourceName}\n${content}\n${MARKER_END}\n`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function rewriteWorkspaceMarker(marker, sources) {
|
|
115
|
+
let workspace = readFileSync(WORKSPACE_FILE, 'utf-8');
|
|
116
|
+
const beginIdx = workspace.indexOf(MARKER_BEGIN);
|
|
117
|
+
const endIdx = workspace.indexOf(MARKER_END);
|
|
118
|
+
const hasExistingBlock = beginIdx >= 0 && endIdx > beginIdx;
|
|
119
|
+
|
|
120
|
+
if (hasExistingBlock) {
|
|
121
|
+
workspace = replaceMarkerBlock(workspace, beginIdx, endIdx, marker);
|
|
122
|
+
} else if (marker) {
|
|
123
|
+
workspace = workspace.replace(/\n*$/, '\n\n') + marker;
|
|
124
|
+
} else {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
writeFileSync(WORKSPACE_FILE, workspace);
|
|
129
|
+
logSyncOutcome(sources);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function replaceMarkerBlock(workspace, beginIdx, endIdx, marker) {
|
|
133
|
+
const before = workspace.slice(0, beginIdx).replace(/\n+$/, '\n');
|
|
134
|
+
const afterStart = endIdx + MARKER_END.length;
|
|
135
|
+
const afterRaw = workspace.slice(afterStart).replace(/^\n+/, '');
|
|
136
|
+
const after = afterRaw ? '\n' + afterRaw : '';
|
|
137
|
+
if (marker) return `${before}\n${marker}${after}`;
|
|
138
|
+
return `${before}${after}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function logSyncOutcome(sources) {
|
|
142
|
+
if (sources.length === 1) {
|
|
143
|
+
console.log(`✓ synced catalogs from .linked/${sources[0].name} into ${WORKSPACE_FILE}`);
|
|
144
|
+
} else {
|
|
145
|
+
console.log(`✓ removed managed catalogs block from ${WORKSPACE_FILE}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Extracts top-level `catalog:` and `catalogs:` blocks from a pnpm-workspace.yaml.
|
|
150
|
+
// Line-based, no YAML parser. Assumes block headers start at column 0 and
|
|
151
|
+
// content is indented (spaces or tabs).
|
|
152
|
+
export function extractCatalogs(yamlContent) {
|
|
153
|
+
const lines = yamlContent.split('\n');
|
|
154
|
+
const out = [];
|
|
155
|
+
let i = 0;
|
|
156
|
+
while (i < lines.length) {
|
|
157
|
+
if (!/^(catalog|catalogs):\s*$/.test(lines[i])) {
|
|
158
|
+
i++;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const start = i;
|
|
162
|
+
i = consumeIndentedLines(lines, i + 1);
|
|
163
|
+
out.push(lines.slice(start, trimTrailingBlanks(lines, start, i)).join('\n'));
|
|
164
|
+
}
|
|
165
|
+
return out.length > 0 ? stripComments(out.join('\n\n')) : null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function stripComments(yaml) {
|
|
169
|
+
return yaml
|
|
170
|
+
.split('\n')
|
|
171
|
+
.filter(line => !/^\s*#/.test(line))
|
|
172
|
+
.join('\n')
|
|
173
|
+
.replace(/\n{3,}/g, '\n\n');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function consumeIndentedLines(lines, from) {
|
|
177
|
+
let i = from;
|
|
178
|
+
while (i < lines.length && (lines[i] === '' || /^[ \t]/.test(lines[i]))) {
|
|
179
|
+
i++;
|
|
180
|
+
}
|
|
181
|
+
return i;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function trimTrailingBlanks(lines, start, end) {
|
|
185
|
+
let trimmed = end;
|
|
186
|
+
while (trimmed > start && lines[trimmed - 1] === '') trimmed--;
|
|
187
|
+
return trimmed;
|
|
188
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
image: node:24
|
|
2
|
+
|
|
3
|
+
options:
|
|
4
|
+
max-time: 15
|
|
5
|
+
|
|
6
|
+
definitions:
|
|
7
|
+
caches:
|
|
8
|
+
pnpm: .pnpm-store
|
|
9
|
+
|
|
10
|
+
steps:
|
|
11
|
+
- step: &check-types
|
|
12
|
+
name: Check types
|
|
13
|
+
runs-on: ['almai', 'self.hosted', 'linux']
|
|
14
|
+
caches:
|
|
15
|
+
- pnpm
|
|
16
|
+
script:
|
|
17
|
+
- corepack enable && corepack prepare pnpm@10.33.0 --activate
|
|
18
|
+
- pnpm config set store-dir .pnpm-store
|
|
19
|
+
- npm config set @bardioc:registry http://nexus.almato.com/repository/npm-group/
|
|
20
|
+
- echo "//nexus.almato.com/repository/npm-group/:_auth=$(echo -n ${NEXUS_REPO_USER}:${NEXUS_REPO_PASS} | base64)" >> ~/.npmrc
|
|
21
|
+
- pnpm install --frozen-lockfile
|
|
22
|
+
- pnpm check-types
|
|
23
|
+
|
|
24
|
+
- step: &build
|
|
25
|
+
name: Build
|
|
26
|
+
runs-on: ['almai', 'self.hosted', 'linux']
|
|
27
|
+
caches:
|
|
28
|
+
- pnpm
|
|
29
|
+
script:
|
|
30
|
+
- corepack enable && corepack prepare pnpm@10.33.0 --activate
|
|
31
|
+
- pnpm config set store-dir .pnpm-store
|
|
32
|
+
- npm config set @bardioc:registry http://nexus.almato.com/repository/npm-group/
|
|
33
|
+
- echo "//nexus.almato.com/repository/npm-group/:_auth=$(echo -n ${NEXUS_REPO_USER}:${NEXUS_REPO_PASS} | base64)" >> ~/.npmrc
|
|
34
|
+
- pnpm install --frozen-lockfile
|
|
35
|
+
- export {{BUILD_ENV}}={{APP_NAME}}
|
|
36
|
+
- pnpm build
|
|
37
|
+
artifacts:
|
|
38
|
+
- {{OUT_DIR}}/**
|
|
39
|
+
|
|
40
|
+
- step: &audit
|
|
41
|
+
name: Security audit
|
|
42
|
+
runs-on: ['almai', 'self.hosted', 'linux']
|
|
43
|
+
caches:
|
|
44
|
+
- pnpm
|
|
45
|
+
script:
|
|
46
|
+
- corepack enable && corepack prepare pnpm@10.33.0 --activate
|
|
47
|
+
- pnpm config set store-dir .pnpm-store
|
|
48
|
+
- npm config set @bardioc:registry http://nexus.almato.com/repository/npm-group/
|
|
49
|
+
- echo "//nexus.almato.com/repository/npm-group/:_auth=$(echo -n ${NEXUS_REPO_USER}:${NEXUS_REPO_PASS} | base64)" >> ~/.npmrc
|
|
50
|
+
- pnpm install --frozen-lockfile
|
|
51
|
+
- pnpm audit --audit-level=high
|
|
52
|
+
|
|
53
|
+
# Publishes any version not yet on Nexus. The bump is done locally (pnpm release:version) and merged
|
|
54
|
+
# in the PR — no git write token, no release gate. Dormant while the app is `"private": true` +
|
|
55
|
+
# unscoped (changeset publish no-ops). To enable: scope the name, set private:false, add
|
|
56
|
+
# publishConfig.registry, and set `files` to the build output.
|
|
57
|
+
- step: &publish
|
|
58
|
+
name: Publish package to Nexus
|
|
59
|
+
runs-on: ['almai', 'self.hosted', 'linux']
|
|
60
|
+
caches:
|
|
61
|
+
- pnpm
|
|
62
|
+
script:
|
|
63
|
+
- corepack enable && corepack prepare pnpm@10.33.0 --activate
|
|
64
|
+
- pnpm config set store-dir .pnpm-store
|
|
65
|
+
- npm config set @bardioc:registry http://nexus.almato.com/repository/npm-group/
|
|
66
|
+
- echo "//nexus.almato.com/repository/npm-group/:_auth=$(echo -n ${NEXUS_REPO_USER}:${NEXUS_REPO_PASS} | base64)" >> ~/.npmrc
|
|
67
|
+
# npm publish auth must live in $HOME/.npmrc — changeset publish runs npm publish from the package dir.
|
|
68
|
+
- echo "//nexus.almato.com/repository/npm-releases/:_auth=$(echo -n ${NEXUS_REPO_USER}:${NEXUS_REPO_PASS} | base64)" >> "$HOME/.npmrc"
|
|
69
|
+
- echo "//nexus.almato.com/repository/npm-releases/:always-auth=true" >> "$HOME/.npmrc"
|
|
70
|
+
- pnpm install --frozen-lockfile
|
|
71
|
+
- export {{BUILD_ENV}}={{APP_NAME}}
|
|
72
|
+
- pnpm build
|
|
73
|
+
# Switch @bardioc to the writable npm-releases repo (npm-group is read-only; @scope:registry wins over publishConfig).
|
|
74
|
+
- npm config set @bardioc:registry http://nexus.almato.com/repository/npm-releases/
|
|
75
|
+
- pnpm changeset publish
|
|
76
|
+
|
|
77
|
+
pipelines:
|
|
78
|
+
# PRs (and pushes to a PR's source branch) run everything except publish.
|
|
79
|
+
pull-requests:
|
|
80
|
+
'**':
|
|
81
|
+
- parallel:
|
|
82
|
+
- step: *check-types
|
|
83
|
+
- step: *build
|
|
84
|
+
- step: *audit
|
|
85
|
+
|
|
86
|
+
# Commits to `dev` run CI then publish. No gate: changeset publish no-ops when nothing is new.
|
|
87
|
+
branches:
|
|
88
|
+
dev:
|
|
89
|
+
- parallel:
|
|
90
|
+
- step: *check-types
|
|
91
|
+
- step: *build
|
|
92
|
+
- step: *audit
|
|
93
|
+
- step:
|
|
94
|
+
<<: *publish
|
|
95
|
+
trigger: automatic
|
|
96
|
+
|
|
97
|
+
custom:
|
|
98
|
+
# On-demand publish — re-run if a publish failed.
|
|
99
|
+
publish-package:
|
|
100
|
+
- step: *publish
|