Skip to content
Merged
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
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
groups:
dev-tooling:
dependency-type: development
open-pull-requests-limit: 5

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
56 changes: 44 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,29 @@ concurrency:
cancel-in-progress: true

jobs:
test:
name: test (node ${{ matrix.node }})
check:
name: lint, types, tests, build
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
node: ['20', '22']
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}
node-version: '22'
cache: npm

- run: npm ci
- run: npx jest --ci
- run: npm run lint
- run: npm run format
- run: npm run typecheck
- run: npx jest --ci --coverage
- run: npm run build

package:
name: the npm tarball holds only what users need
name: exports, types and tarball contents
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand All @@ -48,16 +48,48 @@ jobs:
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm

- run: npm ci

# publint checks package.json against the files; arethetypeswrong checks
# that require() and import each get the right types.
- run: npm run check:package

# A test fixture, a .env or a stray photo in the tarball would be
# published to everyone; fail instead.
- name: Check the packed file list
- name: Only built code, sources and docs are published
run: |
npm pack --dry-run --json > pack.json
npm pack --dry-run --json --ignore-scripts > pack.json
node -e '
const files = require("./pack.json")[0].files.map(f => f.path);
const allowed = /^(src\/[^/]+\.js|LICENSE|README\.md|SECURITY\.md|package\.json)$/;
const allowed = /^(lib\/(commonjs|module|typescript\/(commonjs|module)(\/src)?)\/[^/]+\.(js|js\.map|d\.ts|d\.ts\.map|json)|src\/[^/]+\.ts|CHANGELOG\.md|LICENSE|README\.md|SECURITY\.md|package\.json)$/;
const bad = files.filter(f => !allowed.test(f));
console.log(files.join("\n"));
if (bad.length) { console.error("Not allowed in the package:", bad); process.exit(1); }
if (!files.some(f => f.startsWith("lib/"))) { console.error("No built code in the package"); process.exit(1); }
'

consumer:
name: installs with ${{ matrix.pm }}
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
pm: [npm, yarn1, yarn4-pnp, yarn4-node-modules, pnpm, bun]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm

- run: npm ci
- name: Pack the package as it would be published
run: npm pack
- name: Install it with ${{ matrix.pm }} and load it
run: bash scripts/check-consumer.sh ${{ matrix.pm }} ./molecare-scale-reference-*.tgz
19 changes: 15 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: Release

# Publishes to npm when a GitHub Release is published.
# Publishes to npm when a GitHub Release is published. Every package manager
# (npm, Yarn, pnpm, Bun) and Expo installs from the npm registry, so this one
# publish makes the package available to all of them.
#
# Authentication is npm trusted publishing: GitHub's OIDC token proves the
# package is built by this workflow in this repository, so no npm token is
Expand All @@ -20,7 +22,7 @@ permissions:

jobs:
verify:
name: tag matches the version, tests pass
name: tag matches the version, checks pass
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand All @@ -33,7 +35,7 @@ jobs:
node-version: '22'
cache: npm

- name: Tag matches package.json
- name: Tag matches package.json and the changelog
env:
TAG: ${{ github.event.release.tag_name }}
run: |
Expand All @@ -42,9 +44,16 @@ jobs:
echo "Release tag $TAG does not match package.json version v$VERSION"
exit 1
fi
if ! grep -q "^## $VERSION\b" CHANGELOG.md; then
echo "CHANGELOG.md has no section for $VERSION"
exit 1
fi

- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npx jest --ci
- run: npm run check:package

publish:
name: publish to npm
Expand All @@ -67,4 +76,6 @@ jobs:
registry-url: 'https://registry.npmjs.org'

- run: npm ci --ignore-scripts
- run: npm publish --access public
- run: npm run build
# Built above, once; --ignore-scripts stops prepack building it again.
- run: npm publish --access public --ignore-scripts
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
node_modules/
lib/
coverage/
*.tgz
*.tsbuildinfo
.DS_Store
.env
.env.*
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Changelog

All notable changes to this package are recorded here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
[Semantic Versioning](https://semver.org/).

## 1.0.0

First public release. Breaking changes from 0.x, which was never published.

### Changed

- Written in TypeScript and published as built code: CommonJS and ES modules,
each with its own type declarations, behind an `exports` map. The package
loads from Metro, Node (`require` and `import`), Jest with its default
settings, and TypeScript in `node16` and `bundler` resolution.
- Named functions replace the `ScaleReference` class:
`scaleFromEllipse`, `toMillimetres`, `toSquareMillimetres`,
`scalesAreComparable` and `explainRefusal`. Constants are
`DEFAULT_OPTIONS` and `REFERENCE_OBJECTS`.
- `scaleFromEllipse` always returns a frozen `Scale`. Unreadable input is
`{usable: false, reason: 'invalid_input'}` instead of `null`, and numeric
strings are no longer accepted as numbers.
- `scalesAreComparable` takes `{tolerance}` as an options object and rejects
an unknown option or a tolerance outside 0 to 1.

### Removed

- The default export and the `ScaleReference` class.
- `configure`, `getConfig` and `resetConfig` (already gone in 0.2.0).
54 changes: 35 additions & 19 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Contributing to @molecare/scale-reference

Thanks for being here. This package is a few hundred lines of pure JavaScript
with a fast test suite, so it is a good place for a first contribution.
Thanks for being here. This package is a few hundred lines of TypeScript with a
fast test suite, so it is a good place for a first contribution.

## The one rule that is not negotiable

Expand All @@ -13,32 +13,47 @@ photo. Those numbers carry real error (see "Limits" in the README). Any change
that hides that error, or presents a result as accurate enough for a medical
decision, will be declined, however good the code is.

| Fine | Not fine |
|---|---|
| A new reference object with its published size and a source | A size threshold that labels something as worrying |
| Better handling of tilt, or an uncertainty estimate | Rounding that suggests more precision than the photo holds |
| Clearer messages about why a photo can't be measured | Wording like "accurate" or "clinically validated" |
| Fine | Not fine |
| ----------------------------------------------------------- | ---------------------------------------------------------- |
| A new reference object with its published size and a source | A size threshold that labels something as worrying |
| Better handling of tilt, or an uncertainty estimate | Rounding that suggests more precision than the photo holds |
| Clearer messages about why a photo can't be measured | Wording like "accurate" or "clinically validated" |

If you are unsure which side of the line a change sits on, open an issue and
ask before writing the code.

## Stateless by design
## Design rules

The package keeps no state and has no global settings. Every setting is an
argument, with its default in `src/defaults.js`, and module scope holds frozen
constants only (a test fails on a module-level `let` or `var`). Please don't
add a `configure()`, a cache or a singleton; add an option instead.
- **Stateless.** Pure functions only: no module-level `let` or `var` (a test
checks), no caches, no singletons, no `configure()`. A new setting is an
option with its default in `DEFAULT_OPTIONS`.
- **Frozen outputs and constants.** Results and tables are `Object.freeze`d.
- **A photo that can't be measured is a result, not an error.** Only programming
errors (an unknown or invalid option) throw.
- **Types are part of the API.** A change to an exported type is a change to the
API, and a breaking one needs a major version.

## Getting set up

```bash
git clone https://github.com/MoleCare/rn-scale-reference.git
cd rn-scale-reference
npm ci
npm test
```

You need Node 20 or newer. There is no React Native dependency at all.
You need Node 20.19 or newer to work on it (the tooling needs it; the published
package itself runs on Node 18). There is no React Native dependency at all.

| Command | What it does |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `npm test` | Jest tests (TypeScript, via Babel) |
| `npm run typecheck` | `tsc` in strict mode |
| `npm run lint` / `npm run format` | ESLint (typescript-eslint, strict) and Prettier |
| `npm run build` | Builds `lib/` with react-native-builder-bob: CommonJS, ES modules and types |
| `npm run check:package` | publint and arethetypeswrong on the packed package |
| `npm run check:consumer -- pnpm` | Packs the package, installs it in a fresh project with that package manager (`npm`, `yarn1`, `yarn4-pnp`, `yarn4-node-modules`, `pnpm`, `bun`) and loads it. With `npm` it also checks Jest's default settings and TypeScript. |

CI runs all of these on every pull request.

## Adding a reference object

Expand All @@ -51,15 +66,16 @@ You need Node 20 or newer. There is no React Native dependency at all.
## Pull requests

- One change per pull request, with a test for the behaviour you changed.
- `npm test` passes; CI runs it on Node 20 and 22.
- Add a line to the top section of `CHANGELOG.md` for anything a user would notice.
- Never attach a real photo of a person's skin to an issue or pull request.

## Releases

Maintainers publish to npm from a GitHub Release. The release workflow checks
that the tag matches `package.json`, runs the tests, and publishes with npm
provenance through GitHub's OIDC trusted publishing, so no npm token is stored
anywhere.
Maintainers bump the version in `package.json`, add its `CHANGELOG.md`
section, and publish a GitHub Release tagged `v<version>`. The release workflow
checks the tag and the changelog, runs every check, builds once, and publishes
to npm with provenance through GitHub's OIDC trusted publishing, so no npm
token is stored anywhere.

## Code of conduct

Expand Down
Loading