mirror of
https://github.com/immich-app/immich.git
synced 2026-07-15 21:32:32 +03:00
feat: pump doc references (#29331)
This commit is contained in:
8
.github/workflows/prepare-release.yml
vendored
8
.github/workflows/prepare-release.yml
vendored
@@ -3,8 +3,8 @@ name: Prepare new release
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
serverBump:
|
releaseType:
|
||||||
description: 'Bump server version'
|
description: 'Release type'
|
||||||
required: true
|
required: true
|
||||||
default: 'false'
|
default: 'false'
|
||||||
type: choice
|
type: choice
|
||||||
@@ -77,9 +77,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
env:
|
env:
|
||||||
SERVER_BUMP: ${{ inputs.serverBump }}
|
RELEASE_TYPE: ${{ inputs.releaseType }}
|
||||||
MOBILE_BUMP: ${{ inputs.mobileBump }}
|
MOBILE_BUMP: ${{ inputs.mobileBump }}
|
||||||
run: pnpm --silent release -s "${SERVER_BUMP}" -m "${MOBILE_BUMP}"
|
run: mise //:release --type "${RELEASE_TYPE}" --mobile "${MOBILE_BUMP}"
|
||||||
|
|
||||||
- id: output
|
- id: output
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@@ -28,8 +28,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
root:
|
scripts:
|
||||||
- 'misc/**'
|
- 'packages/scripts/**'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
- 'mise.toml'
|
- 'mise.toml'
|
||||||
i18n:
|
i18n:
|
||||||
@@ -68,10 +68,10 @@ jobs:
|
|||||||
- '.github/workflows/test.yml'
|
- '.github/workflows/test.yml'
|
||||||
force-events: 'workflow_dispatch'
|
force-events: 'workflow_dispatch'
|
||||||
|
|
||||||
root-unit-tests:
|
script-unit-tests:
|
||||||
name: Test the root workspace
|
name: Scripts unit tests
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).root == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).scripts == true }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: pnpm test
|
run: pnpm --filter @immich/scripts test
|
||||||
|
|
||||||
server-unit-tests:
|
server-unit-tests:
|
||||||
name: Test & Lint Server
|
name: Test & Lint Server
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ Our [GitHub Repository](https://github.com/immich-app/immich) is a [monorepo](ht
|
|||||||
| `.vscode/` | VSCode debug launch profiles |
|
| `.vscode/` | VSCode debug launch profiles |
|
||||||
| `packages/cli` | Source code for the CLI |
|
| `packages/cli` | Source code for the CLI |
|
||||||
| `packages/sdk` | Source code for the generated OpenAPI SDK |
|
| `packages/sdk` | Source code for the generated OpenAPI SDK |
|
||||||
|
| `packages/scripts` | Scripts for version bumps and draft releases |
|
||||||
| `docker/` | Docker compose resources for dev, test, production |
|
| `docker/` | Docker compose resources for dev, test, production |
|
||||||
| `design/` | Screenshots and logos for the README |
|
| `design/` | Screenshots and logos for the README |
|
||||||
| `docs/` | Source code for the [https://immich.app](https://immich.app) website |
|
| `docs/` | Source code for the [https://immich.app](https://immich.app) website |
|
||||||
| `machine-learning/` | Source code for the `immich-machine-learning` docker image |
|
| `machine-learning/` | Source code for the `immich-machine-learning` docker image |
|
||||||
| `misc/release/` | Scripts for version bumps and draft releases |
|
|
||||||
| `mobile/` | Source code for the mobile app, both Android and iOS |
|
| `mobile/` | Source code for the mobile app, both Android and iOS |
|
||||||
| `server/` | Source code for the `immich-server` docker image |
|
| `server/` | Source code for the `immich-server` docker image |
|
||||||
| `web/` | Source code for the `web` |
|
| `web/` | Source code for the `web` |
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
#! /usr/bin/env node
|
|
||||||
import { readFileSync, writeFileSync } from 'node:fs';
|
|
||||||
|
|
||||||
const asVersion = (item) => {
|
|
||||||
const { label, url } = item;
|
|
||||||
const [version] = label.substring(1).split('-');
|
|
||||||
const [major, minor, patch] = version.split('.').map(Number);
|
|
||||||
return { major, minor, patch, label, url };
|
|
||||||
};
|
|
||||||
|
|
||||||
const nextVersion = process.argv[2];
|
|
||||||
if (!nextVersion) {
|
|
||||||
console.log('Usage: archive-version.js <version>');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filename = './docs/static/archived-versions.json';
|
|
||||||
let versions = JSON.parse(readFileSync(filename));
|
|
||||||
const newVersion = {
|
|
||||||
label: `v${nextVersion}`,
|
|
||||||
url: `https://docs.v${nextVersion}.archive.immich.app`,
|
|
||||||
};
|
|
||||||
|
|
||||||
let lastVersion = asVersion(newVersion);
|
|
||||||
for (const item of versions) {
|
|
||||||
const version = asVersion(item);
|
|
||||||
// only keep the latest patch version for each minor release
|
|
||||||
if (
|
|
||||||
lastVersion.major === version.major &&
|
|
||||||
lastVersion.minor === version.minor &&
|
|
||||||
lastVersion.patch >= version.patch
|
|
||||||
) {
|
|
||||||
versions = versions.filter((item) => item.label !== version.label);
|
|
||||||
console.log(
|
|
||||||
`Removed ${version.label} (replaced with ${lastVersion.label})`,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastVersion = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileSync(
|
|
||||||
filename,
|
|
||||||
JSON.stringify([newVersion, ...versions], null, 2) + '\n',
|
|
||||||
);
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
#
|
|
||||||
# Pump one or both of the server/mobile versions in appropriate files
|
|
||||||
#
|
|
||||||
# usage: './scripts/pump-version.sh -s <minor|patch|premajor|preminor|prepatch|prerelease> <-m> <true|false>
|
|
||||||
#
|
|
||||||
# examples:
|
|
||||||
# ./scripts/pump-version.sh -s major # 1.0.0+50 => 2.0.0+50
|
|
||||||
# ./scripts/pump-version.sh -s minor -m true # 1.0.0+50 => 1.1.0+51
|
|
||||||
# ./scripts/pump-version.sh -s premajor # 1.0.0+50 => 2.0.0-rc.0+50
|
|
||||||
# ./scripts/pump-version.sh -s prerelease # 2.0.0-rc.0+50 => 2.0.0-rc.1+50
|
|
||||||
# ./scripts/pump-version.sh -m true # 1.0.0+50 => 1.0.0+51
|
|
||||||
#
|
|
||||||
|
|
||||||
SERVER_PUMP="false"
|
|
||||||
MOBILE_PUMP="false"
|
|
||||||
|
|
||||||
while getopts 's:m:' flag; do
|
|
||||||
case "${flag}" in
|
|
||||||
s) SERVER_PUMP=${OPTARG} ;;
|
|
||||||
m) MOBILE_PUMP=${OPTARG} ;;
|
|
||||||
*)
|
|
||||||
echo "Invalid args"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
CURRENT_SERVER=$(jq -r '.version' package.json)
|
|
||||||
if ! NEXT_SERVER=$(pnpm --silent pump "$CURRENT_SERVER" "$SERVER_PUMP"); then
|
|
||||||
echo "Fatal: failed to pump server version: $NEXT_SERVER" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
CURRENT_MOBILE=$(grep "^version: .*+[0-9]\+$" mobile/pubspec.yaml | cut -d "+" -f2)
|
|
||||||
NEXT_MOBILE=$CURRENT_MOBILE
|
|
||||||
|
|
||||||
if [[ $MOBILE_PUMP == "true" ]]; then
|
|
||||||
set $((NEXT_MOBILE++))
|
|
||||||
elif [[ $MOBILE_PUMP == "false" ]]; then
|
|
||||||
echo 'Skipping Mobile Pump'
|
|
||||||
else
|
|
||||||
echo "Fatal: MOBILE_PUMP value $MOBILE_PUMP is invalid"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
|
|
||||||
echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER"
|
|
||||||
|
|
||||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --no-git-checks
|
|
||||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --no-git-checks --prefix server
|
|
||||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --no-git-checks --prefix packages/cli
|
|
||||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --no-git-checks --prefix web
|
|
||||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --no-git-checks --prefix e2e
|
|
||||||
pnpm version "$NEXT_SERVER" --no-git-tag-version --no-git-checks --prefix packages/sdk
|
|
||||||
|
|
||||||
# copy version to open-api spec
|
|
||||||
mise run //:open-api
|
|
||||||
|
|
||||||
uv version --directory machine-learning "$NEXT_SERVER"
|
|
||||||
|
|
||||||
./misc/release/archive-version.js "$NEXT_SERVER"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then
|
|
||||||
echo "Pumping Mobile: $CURRENT_MOBILE => $NEXT_MOBILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
sed -i "s/\"android\.injected\.version\.name\" => \".*\",/\"android\.injected\.version\.name\" => \"$NEXT_SERVER\",/" mobile/android/fastlane/Fastfile
|
|
||||||
sed -i "s/\"android\.injected\.version\.code\" => [0-9]\+,/\"android\.injected\.version\.code\" => $NEXT_MOBILE,/" mobile/android/fastlane/Fastfile
|
|
||||||
sed -i "s/^version: .*+[0-9]\+$/version: $NEXT_SERVER+$NEXT_MOBILE/" mobile/pubspec.yaml
|
|
||||||
# strip prerelease from CFBundleShortVersionString (deploying to testflight _is_ the prerelease)
|
|
||||||
NEXT_SERVER_SHORT="${NEXT_SERVER%%-*}"
|
|
||||||
perl -i -p0e "s/(<key>CFBundleShortVersionString<\/key>\s*<string>).*?(<\/string>)/\${1}$NEXT_SERVER_SHORT\${2}/s" mobile/ios/Runner/Info.plist
|
|
||||||
|
|
||||||
|
|
||||||
echo "IMMICH_VERSION=v$NEXT_SERVER" >>"$GITHUB_ENV"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { pump } from './pump.js';
|
|
||||||
|
|
||||||
const [versionRaw, type] = process.argv.slice(2);
|
|
||||||
const { message, exitCode } = pump(versionRaw, type);
|
|
||||||
|
|
||||||
console.log(message);
|
|
||||||
process.exit(exitCode);
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import semver, { SemVer } from 'semver';
|
|
||||||
|
|
||||||
const printUsage = () => {
|
|
||||||
return {
|
|
||||||
message:
|
|
||||||
'Usage: ./pump_cli.js <semver> <minor|patch|premajor|preminor|prepatch|prerelease|release>',
|
|
||||||
exitCode: 1,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const isPrerelease = (version) => version.prerelease.length > 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {SemVer} version
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
const inc = (version, type) => `v${semver.inc(version, type, {}, 'rc')}`;
|
|
||||||
|
|
||||||
/** @param {string} version */
|
|
||||||
const normalize = (version) => {
|
|
||||||
if (version.startsWith('v')) {
|
|
||||||
version = version.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return version;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} versionRaw
|
|
||||||
* @param {string} type
|
|
||||||
*/
|
|
||||||
export const pump = (versionRaw, type) => {
|
|
||||||
if (!versionRaw) {
|
|
||||||
return printUsage();
|
|
||||||
}
|
|
||||||
|
|
||||||
versionRaw = normalize(versionRaw);
|
|
||||||
|
|
||||||
const version = semver.parse(versionRaw);
|
|
||||||
if (!version) {
|
|
||||||
return printUsage();
|
|
||||||
}
|
|
||||||
|
|
||||||
let newVersionRaw;
|
|
||||||
let valid = true;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'patch':
|
|
||||||
case 'prepatch':
|
|
||||||
case 'minor':
|
|
||||||
case 'preminor':
|
|
||||||
case 'premajor': {
|
|
||||||
newVersionRaw = inc(version, type);
|
|
||||||
// can only use while not in a prerelease
|
|
||||||
valid = !isPrerelease(version);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'prerelease': {
|
|
||||||
newVersionRaw = inc(version, type);
|
|
||||||
// can only use while in a prerelease
|
|
||||||
valid = isPrerelease(version);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'release': {
|
|
||||||
// drop prerelease part
|
|
||||||
newVersionRaw = `${version.major}.${version.minor}.${version.patch}`;
|
|
||||||
// can only use to promote a prerelease to a release (no version change)
|
|
||||||
valid = isPrerelease(version);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
return printUsage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newVersionRaw) {
|
|
||||||
return printUsage();
|
|
||||||
}
|
|
||||||
|
|
||||||
newVersionRaw = normalize(newVersionRaw);
|
|
||||||
|
|
||||||
const newVersion = semver.parse(newVersionRaw);
|
|
||||||
if (!newVersion) {
|
|
||||||
return printUsage();
|
|
||||||
}
|
|
||||||
|
|
||||||
const invalidUpgrade =
|
|
||||||
isPrerelease(version) &&
|
|
||||||
!isPrerelease(newVersion) &&
|
|
||||||
(version.major !== newVersion.major ||
|
|
||||||
version.minor !== newVersion.minor ||
|
|
||||||
version.patch !== newVersion.patch);
|
|
||||||
|
|
||||||
if (!valid || invalidUpgrade) {
|
|
||||||
return {
|
|
||||||
message: `Invalid pump: ${type}. Pumping from ${versionRaw} to ${newVersionRaw} is not allowed.`,
|
|
||||||
exitCode: 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { message: newVersionRaw, exitCode: 0 };
|
|
||||||
};
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
|
||||||
import { pump } from './pump';
|
|
||||||
|
|
||||||
describe(pump.name, () => {
|
|
||||||
describe('usage', () => {
|
|
||||||
it.each([
|
|
||||||
[],
|
|
||||||
['2.7.5'],
|
|
||||||
['2.7.5', 'invalid'],
|
|
||||||
['invalid', 'patch'],
|
|
||||||
['2.7.5', 'major'],
|
|
||||||
])('should not accept $0, $1 as inputs', (version, type) => {
|
|
||||||
expect(pump(version, type)).toEqual({
|
|
||||||
message: expect.stringContaining('Usage: '),
|
|
||||||
exitCode: 1,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('transitions', () => {
|
|
||||||
const valid = [
|
|
||||||
{
|
|
||||||
name: 'patch',
|
|
||||||
items: [['patch', '2.7.5', '2.7.6']],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'prepatch',
|
|
||||||
items: [
|
|
||||||
['prepatch', '2.7.5', '2.7.6-rc.0'],
|
|
||||||
['prerelease', '2.7.6-rc.0', '2.7.6-rc.1'],
|
|
||||||
['release', '2.7.6-rc.1', '2.7.6'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'minor',
|
|
||||||
items: [['minor', '2.7.5', '2.8.0']],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'preminor',
|
|
||||||
items: [
|
|
||||||
['preminor', '2.7.5', '2.8.0-rc.0'],
|
|
||||||
['prerelease', '2.8.0-rc.0', '2.8.0-rc.1'],
|
|
||||||
['release', '2.8.0-rc.1', '2.8.0'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'premajor',
|
|
||||||
items: [
|
|
||||||
['premajor', '2.7.5', '3.0.0-rc.0'],
|
|
||||||
['prerelease', '3.0.0-rc.0', '3.0.0-rc.1'],
|
|
||||||
['release', '3.0.0-rc.1', '3.0.0'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const group of valid) {
|
|
||||||
describe(group.name, () => {
|
|
||||||
it.each(group.items)(
|
|
||||||
'should allow a $0 from $1 to $2',
|
|
||||||
(type, version, next) => {
|
|
||||||
expect(pump(version, type)).toEqual({
|
|
||||||
message: next,
|
|
||||||
exitCode: 0,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('invalid', () => {
|
|
||||||
it.each([
|
|
||||||
['patch', 'v3.0.0-rc.0'],
|
|
||||||
['prepatch', 'v3.0.0-rc.0'],
|
|
||||||
['minor', 'v3.0.0-rc.0'],
|
|
||||||
['preminor', 'v3.0.0-rc.0'],
|
|
||||||
['premajor', 'v3.0.0-rc.0'],
|
|
||||||
['prerelease', 'v3.0.0'],
|
|
||||||
['release', 'v3.0.0'],
|
|
||||||
])('should not allow a $0 on $1', (type, version) => {
|
|
||||||
expect(pump(version, type)).toEqual({
|
|
||||||
message: expect.stringContaining('Invalid pump'),
|
|
||||||
exitCode: 1,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -39,6 +39,14 @@ experimental = true
|
|||||||
pin = true
|
pin = true
|
||||||
lockfile = true
|
lockfile = true
|
||||||
|
|
||||||
|
[tasks.release]
|
||||||
|
run = [
|
||||||
|
"pnpm --filter @immich/scripts install --frozen-lockfile",
|
||||||
|
"pnpm --filter @immich/scripts build",
|
||||||
|
"pnpm --filter @immich/scripts cli release {{arg(name='args', var=true)}}",
|
||||||
|
{ task = "//:open-api" },
|
||||||
|
]
|
||||||
|
|
||||||
[tasks.plugins]
|
[tasks.plugins]
|
||||||
run = [
|
run = [
|
||||||
"pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter @immich/plugin-core install --frozen-lockfile",
|
"pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter @immich/plugin-core install --frozen-lockfile",
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -6,20 +6,14 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "prettier --cache --check i18n/",
|
"format": "prettier --cache --check i18n/",
|
||||||
"format:fix": "prettier --cache --write --list-different i18n",
|
"format:fix": "prettier --cache --write --list-different i18n"
|
||||||
"test": "vitest",
|
|
||||||
"release": "./misc/release/pump-version.sh",
|
|
||||||
"pump": "node ./misc/release/pump-wrapper.js"
|
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.6.0",
|
"packageManager": "pnpm@11.6.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"pnpm": ">=10.0.0"
|
"pnpm": ">=10.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.13.2",
|
|
||||||
"prettier": "^3.8.3",
|
"prettier": "^3.8.3",
|
||||||
"prettier-plugin-sort-json": "^4.2.0",
|
"prettier-plugin-sort-json": "^4.2.0"
|
||||||
"semver": "^7.8.1",
|
|
||||||
"vitest": "^4.1.8"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
packages/scripts/.gitignore
vendored
Normal file
15
packages/scripts/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
*-debug.log
|
||||||
|
*-error.log
|
||||||
|
/.nyc_output
|
||||||
|
/dist
|
||||||
|
/lib
|
||||||
|
/tmp
|
||||||
|
/yarn.lock
|
||||||
|
node_modules
|
||||||
|
oclif.manifest.json
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
/coverage/
|
||||||
|
.reverse-geocoding-dump/
|
||||||
|
upload/
|
||||||
36
packages/scripts/package.json
Normal file
36
packages/scripts/package.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "@immich/scripts",
|
||||||
|
"version": "3.0.0-rc.2",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "pump-wrapper.ts",
|
||||||
|
"license": "GNU Affero General Public License version 3",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/immich-app/immich.git",
|
||||||
|
"directory": "packages/scripts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"cli": "node dist/main.js",
|
||||||
|
"build": "vite build",
|
||||||
|
"watch": "vite build --watch",
|
||||||
|
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
||||||
|
"lint:fix": "pnpm run lint --fix",
|
||||||
|
"test": "vitest",
|
||||||
|
"test:cov": "vitest --coverage",
|
||||||
|
"format": "prettier --cache --check .",
|
||||||
|
"format:fix": "prettier --cache --write --list-different .",
|
||||||
|
"check": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^15.0.0",
|
||||||
|
"semver": "^7.8.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.13.2",
|
||||||
|
"@types/semver": "^7.7.1",
|
||||||
|
"vite": "^8.0.16",
|
||||||
|
"vitest": "^4.1.8"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@11.6.0"
|
||||||
|
}
|
||||||
31
packages/scripts/src/cli.spec.ts
Normal file
31
packages/scripts/src/cli.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { cli } from './cli';
|
||||||
|
|
||||||
|
const run = (args: string[]) => cli(['node', 'pnpm cli', ...args]);
|
||||||
|
|
||||||
|
describe('cli', () => {
|
||||||
|
let exitMock: ReturnType<typeof vi.spyOn>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
exitMock = vi.spyOn(process, 'exit').mockImplementation((code) => {
|
||||||
|
throw new Error(`process.exit(${code})`);
|
||||||
|
});
|
||||||
|
vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('release', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[[], '--type is required'],
|
||||||
|
[['--type'], '--type requires a value'],
|
||||||
|
[['--type', 'invalid'], 'invalid is not an allowed choice'],
|
||||||
|
[['--type', 'major'], 'major is invalid'],
|
||||||
|
])('should not accept %j because %s', (args, _description) => {
|
||||||
|
expect(() => run(['release', ...args])).toThrow();
|
||||||
|
expect(exitMock).toHaveBeenCalledWith(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
54
packages/scripts/src/cli.ts
Normal file
54
packages/scripts/src/cli.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { Command, Option } from 'commander';
|
||||||
|
import { handleRelease } from './commands/release';
|
||||||
|
import {
|
||||||
|
RELEASE_TYPES,
|
||||||
|
ReleaseError,
|
||||||
|
ReleaseInputError,
|
||||||
|
type ReleaseType,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
export const cli = (argv: string[]) => {
|
||||||
|
const program = new Command();
|
||||||
|
|
||||||
|
program
|
||||||
|
.name('pnpm cli')
|
||||||
|
.description('Scripts for managing the repo, releases, etc.')
|
||||||
|
.version('0.1.0');
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('release')
|
||||||
|
.description('pump release versions across relevant files')
|
||||||
|
.addOption(
|
||||||
|
new Option('-t, --type <type>', 'the type of version pump')
|
||||||
|
.choices(RELEASE_TYPES)
|
||||||
|
.makeOptionMandatory(),
|
||||||
|
)
|
||||||
|
.addOption(
|
||||||
|
new Option('-m, --mobile <value>', 'pump mobile build number')
|
||||||
|
.choices(['true', 'false'])
|
||||||
|
.default('false'),
|
||||||
|
)
|
||||||
|
.action(
|
||||||
|
({ type, mobile }: { type: ReleaseType; mobile: 'true' | 'false' }) => {
|
||||||
|
try {
|
||||||
|
console.log(handleRelease({ type, mobile: mobile === 'true' }));
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ReleaseInputError) {
|
||||||
|
console.log(program.usage());
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof ReleaseError) {
|
||||||
|
console.log(
|
||||||
|
`Invalid pump: ${type}. Pumping from ${error.version} to ${error.newVersion} is not allowed.`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return program.parse(argv);
|
||||||
|
};
|
||||||
139
packages/scripts/src/commands/release.spec.ts
Normal file
139
packages/scripts/src/commands/release.spec.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import {
|
||||||
|
resolveArchivedVersions,
|
||||||
|
ArchivedVersion,
|
||||||
|
getNewVersion,
|
||||||
|
ReleaseError,
|
||||||
|
} from './release';
|
||||||
|
|
||||||
|
const archived = (label: string): ArchivedVersion => ({
|
||||||
|
label,
|
||||||
|
url: `https://docs.${label}.archive.immich.app`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const labels = (versions: ArchivedVersion[]) => versions.map((v) => v.label);
|
||||||
|
|
||||||
|
describe(getNewVersion.name, () => {
|
||||||
|
describe('transitions', () => {
|
||||||
|
const valid = [
|
||||||
|
{
|
||||||
|
name: 'patch',
|
||||||
|
items: [['patch', '2.7.5', '2.7.6']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'prepatch',
|
||||||
|
items: [
|
||||||
|
['prepatch', '2.7.5', '2.7.6-rc.0'],
|
||||||
|
['prerelease', '2.7.6-rc.0', '2.7.6-rc.1'],
|
||||||
|
['release', '2.7.6-rc.1', '2.7.6'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'minor',
|
||||||
|
items: [['minor', '2.7.5', '2.8.0']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'preminor',
|
||||||
|
items: [
|
||||||
|
['preminor', '2.7.5', '2.8.0-rc.0'],
|
||||||
|
['prerelease', '2.8.0-rc.0', '2.8.0-rc.1'],
|
||||||
|
['release', '2.8.0-rc.1', '2.8.0'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'premajor',
|
||||||
|
items: [
|
||||||
|
['premajor', '2.7.5', '3.0.0-rc.0'],
|
||||||
|
['prerelease', '3.0.0-rc.0', '3.0.0-rc.1'],
|
||||||
|
['release', '3.0.0-rc.1', '3.0.0'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const group of valid) {
|
||||||
|
describe(group.name, () => {
|
||||||
|
it.each(group.items)(
|
||||||
|
'should allow a $0 from $1 to $2',
|
||||||
|
(type, version, next) => {
|
||||||
|
expect(getNewVersion(version, type)).toEqual(next);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('invalid', () => {
|
||||||
|
it.each([
|
||||||
|
['patch', 'v3.0.0-rc.0'],
|
||||||
|
['prepatch', 'v3.0.0-rc.0'],
|
||||||
|
['minor', 'v3.0.0-rc.0'],
|
||||||
|
['preminor', 'v3.0.0-rc.0'],
|
||||||
|
['premajor', 'v3.0.0-rc.0'],
|
||||||
|
['prerelease', 'v3.0.0'],
|
||||||
|
['release', 'v3.0.0'],
|
||||||
|
])('should not allow a $0 on $1', (type, version) => {
|
||||||
|
expect(() => getNewVersion(version, type)).toThrow(ReleaseError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(resolveArchivedVersions.name, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// silence the "Removed ..." progress logging
|
||||||
|
vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prepend the new version to the front', () => {
|
||||||
|
const result = resolveArchivedVersions([archived('v2.9.0')], '3.0.0');
|
||||||
|
|
||||||
|
expect(result[0]).toEqual({
|
||||||
|
label: 'v3.0.0',
|
||||||
|
url: 'https://docs.v3.0.0.archive.immich.app',
|
||||||
|
});
|
||||||
|
expect(labels(result)).toEqual(['v3.0.0', 'v2.9.0']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle an empty list', () => {
|
||||||
|
expect(labels(resolveArchivedVersions([], '3.0.0'))).toEqual(['v3.0.0']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should drop older patch releases of the new version minor', () => {
|
||||||
|
const versions = ['v3.0.4', 'v3.0.3', 'v3.0.2'].map(archived);
|
||||||
|
|
||||||
|
expect(labels(resolveArchivedVersions(versions, '3.0.5'))).toEqual([
|
||||||
|
'v3.0.5',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep the latest patch of each older minor', () => {
|
||||||
|
const versions = ['v3.1.2', 'v3.1.1', 'v3.0.9', 'v2.0.0'].map(archived);
|
||||||
|
|
||||||
|
expect(labels(resolveArchivedVersions(versions, '3.2.0'))).toEqual([
|
||||||
|
'v3.2.0',
|
||||||
|
'v3.1.2',
|
||||||
|
'v3.0.9',
|
||||||
|
'v2.0.0',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep an older minor when bumping a patch', () => {
|
||||||
|
const versions = ['v3.0.4', 'v3.0.3', 'v2.9.1', 'v2.9.0', 'v1.5.0'].map(
|
||||||
|
archived,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(labels(resolveArchivedVersions(versions, '3.0.5'))).toEqual([
|
||||||
|
'v3.0.5',
|
||||||
|
'v2.9.1',
|
||||||
|
'v1.5.0',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace a prerelease with its release', () => {
|
||||||
|
const versions = ['v3.0.0-rc.2', 'v3.0.0-rc.1', 'v2.9.0'].map(archived);
|
||||||
|
|
||||||
|
expect(labels(resolveArchivedVersions(versions, '3.0.0'))).toEqual([
|
||||||
|
'v3.0.0',
|
||||||
|
'v2.9.0',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
262
packages/scripts/src/commands/release.ts
Normal file
262
packages/scripts/src/commands/release.ts
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
import { appendFileSync } from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import semver, { SemVer } from 'semver';
|
||||||
|
import {
|
||||||
|
JsonFile,
|
||||||
|
RELEASE_TYPES,
|
||||||
|
ReleaseError,
|
||||||
|
ReleaseInputError,
|
||||||
|
ReleaseOptions,
|
||||||
|
ReleaseType,
|
||||||
|
TextFile,
|
||||||
|
} from 'src/types';
|
||||||
|
|
||||||
|
const root = join(dirname(fileURLToPath(import.meta.url)), '../../../');
|
||||||
|
|
||||||
|
const Files = {
|
||||||
|
PackageJson: {
|
||||||
|
Root: join(root, 'package.json'),
|
||||||
|
Rest: ['web', 'packages/cli', 'packages/sdk', 'e2e', 'server'].map(
|
||||||
|
(folder) => join(root, folder, `package.json`),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
ExampleEnv: join(root, 'docker/example.env'),
|
||||||
|
Docs: {
|
||||||
|
Env: join(root, 'docs/docs/install/environment-variables.md'),
|
||||||
|
Upgrading: join(root, 'docs/docs/install/upgrading.md'),
|
||||||
|
ArchivedVersions: join(root, 'docs/static/archived-versions.json'),
|
||||||
|
},
|
||||||
|
Mobile: {
|
||||||
|
Pubspec: join(root, 'mobile/pubspec.yaml'),
|
||||||
|
Fastfile: join(root, 'mobile/android/fastlane/Fastfile'),
|
||||||
|
InfoPlist: join(root, 'mobile/ios/Runner/Info.plist'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleRelease = ({ type, mobile }: ReleaseOptions) => {
|
||||||
|
const versionRaw = getVersion();
|
||||||
|
const newVersionRaw = getNewVersion(versionRaw, type);
|
||||||
|
const newVersion = semver.parse(normalize(newVersionRaw));
|
||||||
|
if (!newVersion) {
|
||||||
|
throw new ReleaseInputError();
|
||||||
|
}
|
||||||
|
const newVersionNoRc = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`;
|
||||||
|
const mobileBuild = getMobileBuild();
|
||||||
|
const newMobileBuild = mobile ? mobileBuild + 1 : mobileBuild;
|
||||||
|
|
||||||
|
// pump versions everywhere
|
||||||
|
|
||||||
|
// package.json
|
||||||
|
for (const file of [Files.PackageJson.Root, ...Files.PackageJson.Rest]) {
|
||||||
|
pump(file, /^ "version": ".*"/m, ` "version": "${newVersionRaw}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// machine-learning
|
||||||
|
execSync(`uv version --directory machine-learning ${newVersionRaw}`, {
|
||||||
|
cwd: root,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
// mobile
|
||||||
|
pump(
|
||||||
|
Files.Mobile.Fastfile,
|
||||||
|
/"android\.injected\.version\.name" => ".*",/g,
|
||||||
|
`"android.injected.version.name" => "${newVersionRaw}",`,
|
||||||
|
);
|
||||||
|
pump(
|
||||||
|
Files.Mobile.Fastfile,
|
||||||
|
/"android\.injected\.version\.code" => \d+,/g,
|
||||||
|
`"android.injected.version.code" => ${newMobileBuild},`,
|
||||||
|
);
|
||||||
|
pump(
|
||||||
|
Files.Mobile.Pubspec,
|
||||||
|
/^version: .*\+\d+$/m,
|
||||||
|
`version: ${newVersionRaw}+${newMobileBuild}`,
|
||||||
|
);
|
||||||
|
// strip prerelease from CFBundleShortVersionString
|
||||||
|
// (deploying to testflight _is_ the prerelease)
|
||||||
|
pump(
|
||||||
|
Files.Mobile.InfoPlist,
|
||||||
|
/(<key>CFBundleShortVersionString<\/key>\s*<string>).*?(<\/string>)/s,
|
||||||
|
`$1${newVersionNoRc}$2`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (type === 'release') {
|
||||||
|
// docker tag references (v2, :v2, etc) in docs
|
||||||
|
const major = `v${newVersion.major}`;
|
||||||
|
|
||||||
|
// sync major tag references in docs and example env file
|
||||||
|
pump(Files.ExampleEnv, /^IMMICH_VERSION=v\d+$/m, `IMMICH_VERSION=${major}`);
|
||||||
|
pump(Files.Docs.Env, /(`IMMICH_VERSION`.*?)`v\d+`/, `$1\`${major}\``);
|
||||||
|
pump(Files.Docs.Upgrading, /:v\d+/, `:${major}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update archived versions list
|
||||||
|
const archivedFile = new JsonFile<ArchivedVersion[]>(
|
||||||
|
Files.Docs.ArchivedVersions,
|
||||||
|
);
|
||||||
|
const versions = archivedFile.read();
|
||||||
|
archivedFile.write(resolveArchivedVersions(versions, newVersionRaw));
|
||||||
|
|
||||||
|
if (process.env.GITHUB_ENV) {
|
||||||
|
// make available for following steps
|
||||||
|
appendFileSync(
|
||||||
|
process.env.GITHUB_ENV,
|
||||||
|
`IMMICH_VERSION=v${newVersionRaw}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVersionRaw;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVersion = () =>
|
||||||
|
new JsonFile<{ version: string }>(Files.PackageJson.Root).read().version;
|
||||||
|
|
||||||
|
export const getNewVersion = (versionRaw: string, type: string) => {
|
||||||
|
if (!versionRaw || !type || !RELEASE_TYPES.includes(type as ReleaseType)) {
|
||||||
|
throw new ReleaseInputError();
|
||||||
|
}
|
||||||
|
|
||||||
|
versionRaw = normalize(versionRaw);
|
||||||
|
|
||||||
|
const version = semver.parse(versionRaw);
|
||||||
|
if (!version) {
|
||||||
|
throw new ReleaseInputError();
|
||||||
|
}
|
||||||
|
|
||||||
|
let newVersionRaw;
|
||||||
|
let valid = true;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'patch':
|
||||||
|
case 'prepatch':
|
||||||
|
case 'minor':
|
||||||
|
case 'preminor':
|
||||||
|
case 'premajor': {
|
||||||
|
newVersionRaw = inc(version, type);
|
||||||
|
// can only use while not in a prerelease
|
||||||
|
valid = !isPrerelease(version);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'prerelease': {
|
||||||
|
newVersionRaw = inc(version, type);
|
||||||
|
// can only use while in a prerelease
|
||||||
|
valid = isPrerelease(version);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'release': {
|
||||||
|
// drop prerelease part
|
||||||
|
newVersionRaw = `${version.major}.${version.minor}.${version.patch}`;
|
||||||
|
// can only use to promote a prerelease to a release (no version change)
|
||||||
|
valid = isPrerelease(version);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
throw new ReleaseInputError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newVersionRaw) {
|
||||||
|
throw new ReleaseInputError();
|
||||||
|
}
|
||||||
|
|
||||||
|
newVersionRaw = normalize(newVersionRaw);
|
||||||
|
|
||||||
|
const newVersion = semver.parse(newVersionRaw);
|
||||||
|
if (!newVersion) {
|
||||||
|
throw new ReleaseInputError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidUpgrade =
|
||||||
|
isPrerelease(version) &&
|
||||||
|
!isPrerelease(newVersion) &&
|
||||||
|
(version.major !== newVersion.major ||
|
||||||
|
version.minor !== newVersion.minor ||
|
||||||
|
version.patch !== newVersion.patch);
|
||||||
|
|
||||||
|
if (!valid || invalidUpgrade) {
|
||||||
|
throw new ReleaseError({
|
||||||
|
version: versionRaw,
|
||||||
|
newVersion: newVersionRaw,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVersionRaw;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMobileBuild = () => {
|
||||||
|
const pubspec = new TextFile(Files.Mobile.Pubspec).read();
|
||||||
|
const match = pubspec.match(/^version: .*\+(\d+)$/m);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error('Could not find mobile build number in pubspec.yaml');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(match[1]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pump = (path: string, pattern: RegExp, replacement: string) => {
|
||||||
|
const file = new TextFile(path);
|
||||||
|
const update = file.read().replace(pattern, replacement);
|
||||||
|
file.write(update);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ArchivedVersion {
|
||||||
|
label: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const resolveArchivedVersions = (
|
||||||
|
versions: ArchivedVersion[],
|
||||||
|
nextVersion: string,
|
||||||
|
): ArchivedVersion[] => {
|
||||||
|
const newVersion: ArchivedVersion = {
|
||||||
|
label: `v${nextVersion}`,
|
||||||
|
url: `https://docs.v${nextVersion}.archive.immich.app`,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = versions;
|
||||||
|
let lastVersion = asVersion(newVersion);
|
||||||
|
for (const item of versions) {
|
||||||
|
const version = asVersion(item);
|
||||||
|
// only keep the latest patch version for each minor release
|
||||||
|
if (
|
||||||
|
lastVersion.major === version.major &&
|
||||||
|
lastVersion.minor === version.minor &&
|
||||||
|
lastVersion.patch >= version.patch
|
||||||
|
) {
|
||||||
|
result = result.filter((item) => item.label !== version.label);
|
||||||
|
console.log(
|
||||||
|
`Removed ${version.label} (replaced with ${lastVersion.label})`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastVersion = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [newVersion, ...result];
|
||||||
|
};
|
||||||
|
|
||||||
|
const asVersion = (item: ArchivedVersion) => {
|
||||||
|
const { label, url } = item;
|
||||||
|
const [version] = label.substring(1).split('-');
|
||||||
|
const [major, minor, patch] = version.split('.').map(Number);
|
||||||
|
return { major, minor, patch, label, url };
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPrerelease = (version: SemVer) => version.prerelease.length > 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {SemVer} version
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
const inc = (version: SemVer, type: ReleaseType) =>
|
||||||
|
`v${semver.inc(version, type, {}, 'rc')}`;
|
||||||
|
|
||||||
|
const normalize = (version: string) =>
|
||||||
|
version.startsWith('v') ? version.slice(1) : version;
|
||||||
3
packages/scripts/src/main.ts
Normal file
3
packages/scripts/src/main.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { cli } from './cli';
|
||||||
|
|
||||||
|
cli(process.argv);
|
||||||
55
packages/scripts/src/types.ts
Normal file
55
packages/scripts/src/types.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { readFileSync, writeFileSync } from 'node:fs';
|
||||||
|
|
||||||
|
export const RELEASE_TYPES = [
|
||||||
|
'minor',
|
||||||
|
'patch',
|
||||||
|
'premajor',
|
||||||
|
'preminor',
|
||||||
|
'prepatch',
|
||||||
|
'prerelease',
|
||||||
|
'release',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type ReleaseType = (typeof RELEASE_TYPES)[number];
|
||||||
|
export type ReleaseOptions = { type: string; mobile: boolean };
|
||||||
|
|
||||||
|
export class TextFile {
|
||||||
|
constructor(private file: string) {}
|
||||||
|
|
||||||
|
read() {
|
||||||
|
return readFileSync(this.file, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
write(contents: string) {
|
||||||
|
writeFileSync(this.file, contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JsonFile<T extends object> {
|
||||||
|
private text: TextFile;
|
||||||
|
|
||||||
|
constructor(private file: string) {
|
||||||
|
this.text = new TextFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
read() {
|
||||||
|
return JSON.parse(this.text.read()) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
write<T>(contents: T) {
|
||||||
|
return this.text.write(JSON.stringify(contents, null, 2) + '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReleaseInputError extends Error {}
|
||||||
|
export class ReleaseError extends Error {
|
||||||
|
version: string;
|
||||||
|
newVersion: string;
|
||||||
|
|
||||||
|
constructor(options: { version: string; newVersion: string }) {
|
||||||
|
super(`Invalid pump`);
|
||||||
|
|
||||||
|
this.version = options.version;
|
||||||
|
this.newVersion = options.newVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
packages/scripts/tsconfig.json
Normal file
26
packages/scripts/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"declaration": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"incremental": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"paths": {
|
||||||
|
"src/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"removeComments": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"rootDir": "./src",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"target": "es2023",
|
||||||
|
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo",
|
||||||
|
"types": ["vitest/globals", "node"]
|
||||||
|
},
|
||||||
|
"exclude": ["dist", "node_modules", "vite.config.ts"]
|
||||||
|
}
|
||||||
25
packages/scripts/vite.config.ts
Normal file
25
packages/scripts/vite.config.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { defineConfig, UserConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: { src: '/src' },
|
||||||
|
tsconfigPaths: true,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
rolldownOptions: {
|
||||||
|
input: 'src/main.ts',
|
||||||
|
output: {
|
||||||
|
dir: 'dist',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ssr: true,
|
||||||
|
},
|
||||||
|
ssr: {
|
||||||
|
// bundle everything except for Node built-ins
|
||||||
|
noExternal: /^(?!node:).*$/,
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
name: 'scripts:unit',
|
||||||
|
globals: true,
|
||||||
|
},
|
||||||
|
} as UserConfig);
|
||||||
623
pnpm-lock.yaml
generated
623
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
|||||||
import { defineConfig } from 'vitest/config';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
test: {
|
|
||||||
include: ['misc/**/*.spec.js'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user