electron build changes

This commit is contained in:
Ramnique Singh 2026-02-06 05:58:21 +05:30
parent dbdfcffa8d
commit 4386cc62a1
6 changed files with 355 additions and 32 deletions

View file

@ -8,7 +8,7 @@ permissions:
contents: write # Required to upload release assets
jobs:
build:
build-macos:
runs-on: macos-latest
steps:
@ -75,7 +75,7 @@ jobs:
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Add keychain to search list
security list-keychain -d user -s "$KEYCHAIN_PATH" login.keychain
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain
# Verify certificate was imported
security find-identity -v "$KEYCHAIN_PATH"
@ -87,32 +87,16 @@ jobs:
run: pnpm install --frozen-lockfile
working-directory: apps/x
- name: Build and publish to S3
- name: Build electron app
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
VITE_PUBLIC_POSTHOG_KEY: ${{ secrets.VITE_PUBLIC_POSTHOG_KEY }}
VITE_PUBLIC_POSTHOG_HOST: ${{ secrets.VITE_PUBLIC_POSTHOG_HOST }}
run: npm run publish
run: electron-forge publish --arch=arm64,x64 --platform=darwin
working-directory: apps/x/apps/main
- name: Upload workflow artifacts
uses: actions/upload-artifact@v4
with:
name: distributables
path: apps/x/apps/main/out/make/*
retention-days: 30
- name: Attach files to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: apps/x/apps/main/out/make/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cleanup keychain
if: always()
run: |
@ -120,3 +104,140 @@ jobs:
if [ -f "$KEYCHAIN_PATH" ]; then
security delete-keychain "$KEYCHAIN_PATH" || true
fi
- name: Upload workflow artifacts
uses: actions/upload-artifact@v4
with:
name: distributables
path: apps/x/apps/main/out/make/*
retention-days: 30
build-linux:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'pnpm'
cache-dependency-path: 'apps/x/pnpm-lock.yaml'
- name: Extract version from tag
id: version
run: |
VERSION="${GITHUB_REF#refs/tags/v}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "Extracted version: ${VERSION}"
- name: Update package.json versions
run: |
node -e "
const fs = require('fs');
const version = '${{ steps.version.outputs.version }}';
// Update apps/x/package.json
const rootPackage = JSON.parse(fs.readFileSync('apps/x/package.json', 'utf8'));
rootPackage.version = version;
fs.writeFileSync('apps/x/package.json', JSON.stringify(rootPackage, null, 2) + '\n');
// Update apps/x/apps/main/package.json
const mainPackage = JSON.parse(fs.readFileSync('apps/x/apps/main/package.json', 'utf8'));
mainPackage.version = version;
fs.writeFileSync('apps/x/apps/main/package.json', JSON.stringify(mainPackage, null, 2) + '\n');
console.log('Updated version to:', version);
"
- name: Install dependencies
run: pnpm install --frozen-lockfile
working-directory: apps/x
- name: Build electron app
env:
VITE_PUBLIC_POSTHOG_KEY: ${{ secrets.VITE_PUBLIC_POSTHOG_KEY }}
VITE_PUBLIC_POSTHOG_HOST: ${{ secrets.VITE_PUBLIC_POSTHOG_HOST }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: electron-forge publish --arch=x64,arm64 --platform=linux
working-directory: apps/x/apps/main
- name: Upload workflow artifacts
uses: actions/upload-artifact@v4
with:
name: distributables-linux
path: apps/x/apps/main/out/make/*
retention-days: 30
build-windows:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'pnpm'
cache-dependency-path: 'apps/x/pnpm-lock.yaml'
- name: Extract version from tag
id: version
shell: bash
run: |
VERSION="${GITHUB_REF#refs/tags/v}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "Extracted version: ${VERSION}"
- name: Update package.json versions
shell: bash
run: |
node -e "
const fs = require('fs');
const version = '${{ steps.version.outputs.version }}';
// Update apps/x/package.json
const rootPackage = JSON.parse(fs.readFileSync('apps/x/package.json', 'utf8'));
rootPackage.version = version;
fs.writeFileSync('apps/x/package.json', JSON.stringify(rootPackage, null, 2) + '\n');
// Update apps/x/apps/main/package.json
const mainPackage = JSON.parse(fs.readFileSync('apps/x/apps/main/package.json', 'utf8'));
mainPackage.version = version;
fs.writeFileSync('apps/x/apps/main/package.json', JSON.stringify(mainPackage, null, 2) + '\n');
console.log('Updated version to:', version);
"
- name: Install dependencies
run: pnpm install --frozen-lockfile
working-directory: apps/x
- name: Build electron app
env:
VITE_PUBLIC_POSTHOG_KEY: ${{ secrets.VITE_PUBLIC_POSTHOG_KEY }}
VITE_PUBLIC_POSTHOG_HOST: ${{ secrets.VITE_PUBLIC_POSTHOG_HOST }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: electron-forge publish --arch=x64 --platform=win32
working-directory: apps/x/apps/main
- name: Upload workflow artifacts
uses: actions/upload-artifact@v4
with:
name: distributables-windows
path: apps/x/apps/main/out/make/*
retention-days: 30

View file

@ -22,7 +22,7 @@ await esbuild.build({
platform: 'node',
target: 'node20',
outfile: './.package/dist/main.cjs',
external: ['electron'], // Provided by Electron runtime
external: ['electron', 'electron-squirrel-startup'], // Provided by Electron runtime
// Use CommonJS format - many dependencies use require() which doesn't work
// well with esbuild's ESM shim. CJS handles dynamic requires natively.
format: 'cjs',

View file

@ -19,9 +19,6 @@ module.exports = {
appleIdPassword: process.env.APPLE_PASSWORD,
teamId: process.env.APPLE_TEAM_ID
},
// NOTE: Electron Forge ignores packagerConfig.dir and always packages from the
// config file's directory. We use packageAfterCopy hook instead to customize output.
// dir: path.join(__dirname, '.package'), // Not supported by Forge
// Since we bundle everything with esbuild, we don't need node_modules at all.
// These settings prevent Forge's dependency walker (flora-colossus) from trying
// to analyze/copy node_modules, which fails with pnpm's symlinked workspaces.
@ -42,9 +39,16 @@ module.exports = {
name: `Rowboat-${arch}`, // Architecture-specific name to avoid conflicts
})
},
{
name: '@electron-forge/maker-squirrel',
config: () => ({
authors: 'Rowboat',
description: 'AI coworker with memory',
})
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
platform: ["darwin", "win32", "linux"],
// ZIP is used by Squirrel.Mac for auto-updates
config: (arch) => ({
// Path must match S3 publisher's folder structure: releases/darwin/{arch}
@ -61,6 +65,16 @@ module.exports = {
public: true,
folder: 'releases' // Creates structure: releases/darwin/{arch}/files (separate builds for arm64 and x64)
}
},
{
name: '@electron-forge/publisher-github',
config: {
repository: {
owner: 'rowboatlabs',
name: 'rowboat'
},
prerelease: true
}
}
],
hooks: {

View file

@ -6,9 +6,8 @@
"scripts": {
"start": "electron .",
"build": "rm -rf dist && tsc && node bundle.mjs",
"package": "electron-forge package --arch=arm64,x64 --platform=darwin",
"make": "electron-forge make --arch=arm64,x64 --platform=darwin",
"publish": "electron-forge publish --arch=arm64,x64 --platform=darwin"
"package": "electron-forge package",
"make": "electron-forge make"
},
"dependencies": {
"@x/core": "workspace:*",
@ -18,14 +17,15 @@
"zod": "^4.2.1"
},
"devDependencies": {
"@types/node": "^25.0.3",
"electron": "^39.2.7",
"esbuild": "^0.24.2",
"@electron-forge/cli": "^7.10.2",
"@electron-forge/maker-deb": "^7.10.2",
"@electron-forge/maker-dmg": "^7.10.2",
"@electron-forge/maker-squirrel": "^7.10.2",
"@electron-forge/maker-zip": "^7.10.2",
"@electron-forge/publisher-s3": "^7.10.2"
"@electron-forge/publisher-github": "^7.11.1",
"@electron-forge/publisher-s3": "^7.10.2",
"@types/node": "^25.0.3",
"electron": "^39.2.7",
"esbuild": "^0.24.2"
}
}

View file

@ -16,6 +16,9 @@ import { initConfigs } from "@x/core/dist/config/initConfigs.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// run this as early in the main process as possible
if (require('electron-squirrel-startup')) app.quit();
// Path resolution differs between development and production:
const preloadPath = app.isPackaged
? path.join(__dirname, "../preload/dist/preload.js")

185
apps/x/pnpm-lock.yaml generated
View file

@ -72,6 +72,9 @@ importers:
'@electron-forge/maker-zip':
specifier: ^7.10.2
version: 7.11.1
'@electron-forge/publisher-github':
specifier: ^7.11.1
version: 7.11.1
'@electron-forge/publisher-s3':
specifier: ^7.10.2
version: 7.11.1
@ -734,6 +737,10 @@ packages:
resolution: {integrity: sha512-rXE9oMFGMtdQrixnumWYH5TTGsp99iPHZb3jI74YWq518ctCh6DlIgWlhf6ok2X0+lhWovcIb45KJucUFAQ13w==}
engines: {node: '>= 16.4.0'}
'@electron-forge/publisher-github@7.11.1':
resolution: {integrity: sha512-3S7DS1NZRrYvf59eqH0F2ke9oLD5FQqW5+t6kY1EuEo6I8HF+u6dOkGnvzhRh+uvKkjy4ynV3j735PlqBbClGQ==}
engines: {node: '>= 16.4.0'}
'@electron-forge/publisher-s3@7.11.1':
resolution: {integrity: sha512-80XQnCC6SvzX96Y2uW0nsm7cLuN3S8W1OeS+DdEb8bITR+o017PFOjfs2634DYsTYdx2+TFtpadVhUI04ATdtQ==}
engines: {node: '>= 16.4.0'}
@ -1370,6 +1377,70 @@ packages:
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
deprecated: This functionality has been moved to @npmcli/fs
'@octokit/auth-token@4.0.0':
resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==}
engines: {node: '>= 18'}
'@octokit/core@5.2.2':
resolution: {integrity: sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==}
engines: {node: '>= 18'}
'@octokit/endpoint@9.0.6':
resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==}
engines: {node: '>= 18'}
'@octokit/graphql@7.1.1':
resolution: {integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==}
engines: {node: '>= 18'}
'@octokit/openapi-types@12.11.0':
resolution: {integrity: sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==}
'@octokit/openapi-types@24.2.0':
resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==}
'@octokit/plugin-paginate-rest@11.4.4-cjs.2':
resolution: {integrity: sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': '5'
'@octokit/plugin-request-log@4.0.1':
resolution: {integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': '5'
'@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1':
resolution: {integrity: sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': ^5
'@octokit/plugin-retry@6.1.0':
resolution: {integrity: sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': '5'
'@octokit/request-error@5.1.1':
resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==}
engines: {node: '>= 18'}
'@octokit/request@8.4.1':
resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==}
engines: {node: '>= 18'}
'@octokit/rest@20.1.2':
resolution: {integrity: sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==}
engines: {node: '>= 18'}
'@octokit/types@13.10.0':
resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==}
'@octokit/types@6.41.0':
resolution: {integrity: sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==}
'@openrouter/ai-sdk-provider@1.5.4':
resolution: {integrity: sha512-xrSQPUIH8n9zuyYZR0XK7Ba0h2KsjJcMkxnwaYfmv13pKs3sDkjPzVPPhlhzqBGddHb5cFEwJ9VFuFeDcxCDSw==}
engines: {node: '>=18'}
@ -3163,6 +3234,9 @@ packages:
resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==}
hasBin: true
before-after-hook@2.2.3:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
@ -3186,6 +3260,9 @@ packages:
resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
bottleneck@2.19.5:
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
bowser@2.13.1:
resolution: {integrity: sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==}
@ -3703,6 +3780,9 @@ packages:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
deprecation@2.3.1:
resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==}
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@ -6443,6 +6523,9 @@ packages:
unist-util-visit@5.0.0:
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
universal-user-agent@6.0.1:
resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==}
universalify@0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
@ -7591,6 +7674,24 @@ snapshots:
- bluebird
- supports-color
'@electron-forge/publisher-github@7.11.1':
dependencies:
'@electron-forge/publisher-base': 7.11.1
'@electron-forge/shared-types': 7.11.1
'@octokit/core': 5.2.2
'@octokit/plugin-retry': 6.1.0(@octokit/core@5.2.2)
'@octokit/request-error': 5.1.1
'@octokit/rest': 20.1.2
'@octokit/types': 6.41.0
chalk: 4.1.2
debug: 4.4.3
fs-extra: 10.1.0
log-symbols: 4.1.0
mime-types: 2.1.35
transitivePeerDependencies:
- bluebird
- supports-color
'@electron-forge/publisher-s3@7.11.1':
dependencies:
'@aws-sdk/client-s3': 3.971.0
@ -8286,6 +8387,82 @@ snapshots:
mkdirp: 1.0.4
rimraf: 3.0.2
'@octokit/auth-token@4.0.0': {}
'@octokit/core@5.2.2':
dependencies:
'@octokit/auth-token': 4.0.0
'@octokit/graphql': 7.1.1
'@octokit/request': 8.4.1
'@octokit/request-error': 5.1.1
'@octokit/types': 13.10.0
before-after-hook: 2.2.3
universal-user-agent: 6.0.1
'@octokit/endpoint@9.0.6':
dependencies:
'@octokit/types': 13.10.0
universal-user-agent: 6.0.1
'@octokit/graphql@7.1.1':
dependencies:
'@octokit/request': 8.4.1
'@octokit/types': 13.10.0
universal-user-agent: 6.0.1
'@octokit/openapi-types@12.11.0': {}
'@octokit/openapi-types@24.2.0': {}
'@octokit/plugin-paginate-rest@11.4.4-cjs.2(@octokit/core@5.2.2)':
dependencies:
'@octokit/core': 5.2.2
'@octokit/types': 13.10.0
'@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.2)':
dependencies:
'@octokit/core': 5.2.2
'@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1(@octokit/core@5.2.2)':
dependencies:
'@octokit/core': 5.2.2
'@octokit/types': 13.10.0
'@octokit/plugin-retry@6.1.0(@octokit/core@5.2.2)':
dependencies:
'@octokit/core': 5.2.2
'@octokit/request-error': 5.1.1
'@octokit/types': 13.10.0
bottleneck: 2.19.5
'@octokit/request-error@5.1.1':
dependencies:
'@octokit/types': 13.10.0
deprecation: 2.3.1
once: 1.4.0
'@octokit/request@8.4.1':
dependencies:
'@octokit/endpoint': 9.0.6
'@octokit/request-error': 5.1.1
'@octokit/types': 13.10.0
universal-user-agent: 6.0.1
'@octokit/rest@20.1.2':
dependencies:
'@octokit/core': 5.2.2
'@octokit/plugin-paginate-rest': 11.4.4-cjs.2(@octokit/core@5.2.2)
'@octokit/plugin-request-log': 4.0.1(@octokit/core@5.2.2)
'@octokit/plugin-rest-endpoint-methods': 13.3.2-cjs.1(@octokit/core@5.2.2)
'@octokit/types@13.10.0':
dependencies:
'@octokit/openapi-types': 24.2.0
'@octokit/types@6.41.0':
dependencies:
'@octokit/openapi-types': 12.11.0
'@openrouter/ai-sdk-provider@1.5.4(ai@5.0.117(zod@4.2.1))(zod@4.2.1)':
dependencies:
'@openrouter/sdk': 0.1.27
@ -10318,6 +10495,8 @@ snapshots:
baseline-browser-mapping@2.9.11: {}
before-after-hook@2.2.3: {}
bidi-js@1.0.3:
dependencies:
require-from-string: 2.0.2
@ -10351,6 +10530,8 @@ snapshots:
boolean@3.2.0:
optional: true
bottleneck@2.19.5: {}
bowser@2.13.1: {}
bplist-creator@0.0.8:
@ -10902,6 +11083,8 @@ snapshots:
depd@2.0.0: {}
deprecation@2.3.1: {}
dequal@2.0.3: {}
detect-libc@2.1.2: {}
@ -14293,6 +14476,8 @@ snapshots:
unist-util-is: 6.0.1
unist-util-visit-parents: 6.0.2
universal-user-agent@6.0.1: {}
universalify@0.1.2: {}
universalify@2.0.1: {}