Skip to content
Closed
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
11 changes: 7 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ jobs:
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run prettier
- run: npm run lint
- run: npm run test
- run: npm run build
# Deliberately not `npm run verify`: the install-based export smokes and the
# Next build smokes need the npm registry, and a registry blip must not fail a
# release. PR CI (test.yml) gates those on the same content; keep the required
# status checks enabled on main so nothing reaches here unverified. This job
# still builds and asserts the published layout offline.
- run: npm run verify:release
- name: semantic release
uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6
with:
Expand Down
98 changes: 97 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Test
on:
pull_request:
branches:
- "**"
- '**'

concurrency:
group: tests-${{ github.ref }}
Expand All @@ -21,8 +21,104 @@ jobs:
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run prettier
- run: npm run lint
- run: npm run test
- run: npm run build
- run: npm run test:exports
- name: Pack package for smoke tests
run: |
mkdir -p artifacts
npm pack --silent --pack-destination artifacts
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: packed-package
path: artifacts/*.tgz
if-no-files-found: error

package-smoke:
name: Package smoke (Node ${{ matrix.node }})
needs: test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Node 24 is already covered by the lint/test job's export smoke.
node: [20.9.0, 22]

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: ${{ matrix.node }}
# Warms ~/.npm from the lockfile so the fixture installs below can run
# --prefer-offline instead of hitting the registry for pinned peers.
cache: npm
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: packed-package
path: artifacts
- name: Verify packed package
run: |
tarball="$(ls artifacts/*.tgz)"
node scripts/verify-packed-package.mjs --tarball "$tarball"

next-build:
name: Next build smoke (next@${{ matrix.next }})
needs: test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Both ends of the `next` peer range, each built on Turbopack AND webpack.
# Node-level resolution checks cannot catch bundler-specific breakage:
# fully-specified 'next/headers.js' imports pass under Node and webpack but
# make Turbopack miss its react-server aliases, failing the build.
next: ['15.5.12', '16.2.12']

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
cache: npm
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: packed-package
path: artifacts
- name: Verify Next build
run: |
tarball="$(ls artifacts/*.tgz)"
node scripts/verify-next-build.mjs --tarball "$tarball" --next '${{ matrix.next }}'

# Single stable context to require in branch protection on main. The jobs above
# carry matrix values in their names ("Next build smoke (next@16.2.12)"), so
# requiring them directly would break protection every time a matrix value is
# bumped: the old context stops reporting and blocks every PR. Require only "CI".
#
# release.yml deliberately skips the registry-dependent smokes and trusts that
# this gate ran on the same content, so keep it required.
ci:
name: CI
if: always()
needs: [test, package-smoke, next-build]
runs-on: ubuntu-latest

steps:
- name: Verify all required jobs succeeded
env:
RESULTS: ${{ join(needs.*.result, ' ') }}
run: |
echo "dependency results: $RESULTS"
for result in $RESULTS; do
if [ "$result" != "success" ]; then
echo "::error::a required job did not succeed ($RESULTS)"
exit 1
fi
done
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*


# packed tarballs
*.tgz
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,21 @@ A collection of general purpose utilities and helpers for web projects.
npm install @smartive/utils
```

Requires **Node.js 20.9+** (breaking change if you are still on older Node). The package ships
dual ESM and CommonJS builds for every public entry point (`.`, `/http`, `/datocms`, `/next`),
so both `import` and `require` work:

```typescript
// ESM
import { createDatoClient } from '@smartive/utils/datocms';

// CommonJS
const { createDatoClient } = require('@smartive/utils/datocms');
```

The root export (`@smartive/utils`) stays dependency-free. Optional peer dependencies are only
required when you import the corresponding subpath.
required when you import the corresponding subpath. One caveat applies to `/next` — see
[`@smartive/utils/next`](#smartiveutilsnext) below.

## Utilities

Expand Down Expand Up @@ -86,6 +99,12 @@ Next.js App Router helpers for draft mode, DatoCMS web previews, and cache reval
npm install next
```

**Requires a bundler.** This subpath imports `next/headers`, `next/navigation`, and `next/server`
as bare specifiers. Next's bundlers (Turbopack and webpack) resolve those, but Node's ESM loader
cannot, because Next ships no package `exports` map. Inside a Next app — the only place these APIs
work, since they need a request context — this is transparent. Outside one, `require()` resolves
but a raw `import` does not.

```typescript
// app/api/draft/enable/route.ts
import { createDraftHandlers } from '@smartive/utils/next';
Expand Down
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 59 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,84 @@
"type": "module",
"source": "./src/index.ts",
"sideEffects": false,
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/cjs/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/cjs/index.d.ts",
"default": "./dist/cjs/index.js"
},
"default": "./dist/esm/index.js"
},
"./http": {
"types": "./dist/http/index.d.ts",
"import": "./dist/http/index.js"
"import": {
"types": "./dist/esm/http/index.d.ts",
"default": "./dist/esm/http/index.js"
},
"require": {
"types": "./dist/cjs/http/index.d.ts",
"default": "./dist/cjs/http/index.js"
},
"default": "./dist/esm/http/index.js"
},
"./datocms": {
"types": "./dist/datocms/index.d.ts",
"import": "./dist/datocms/index.js"
"import": {
"types": "./dist/esm/datocms/index.d.ts",
"default": "./dist/esm/datocms/index.js"
},
"require": {
"types": "./dist/cjs/datocms/index.d.ts",
"default": "./dist/cjs/datocms/index.js"
},
"default": "./dist/esm/datocms/index.js"
},
"./next": {
"types": "./dist/next/index.d.ts",
"import": "./dist/next/index.js"
"import": {
"types": "./dist/esm/next/index.d.ts",
"default": "./dist/esm/next/index.js"
},
"require": {
"types": "./dist/cjs/next/index.d.ts",
"default": "./dist/cjs/next/index.js"
},
"default": "./dist/esm/next/index.js"
}
},
"files": [
"dist/**/*",
"src/**/*",
"!src/**/*.test.ts"
],
"typesVersions": {
"*": {
"http": ["./dist/cjs/http/index.d.ts"],
"datocms": ["./dist/cjs/datocms/index.d.ts"],
"next": ["./dist/cjs/next/index.d.ts"]
}
},
"engines": {
"node": ">=20.9.0"
},
"scripts": {
"clean": "rimraf dist",
"prebuild": "npm run clean",
"build": "tsc -p tsconfig.build.json",
"build:esm": "tsc -p tsconfig.esm.json",
"build:cjs": "tsc -p tsconfig.cjs.json && node scripts/write-cjs-package.mjs",
"build": "npm run build:esm && npm run build:cjs",
"lint": "eslint src",
"prettier": "prettier --check src",
"test": "vitest run"
"prettier": "prettier --check src scripts README.md",
"test": "vitest run",
"test:exports": "node scripts/verify-packed-package.mjs",
"test:exports:layout": "node scripts/verify-packed-package.mjs --layout-only",
"test:next-build": "node scripts/verify-next-build.mjs",
"verify": "npm run prettier && npm run lint && npm run test && npm run build && npm run test:exports",
"verify:release": "npm run prettier && npm run lint && npm run test && npm run build && npm run test:exports:layout"
},
"publishConfig": {
"access": "public"
Expand Down
Loading
Loading