replace uuid and fs-extra with builtins (#7529)

* fs-extra

* uuid

* note
This commit is contained in:
Matt Fiddaman
2026-04-16 20:42:27 +00:00
committed by GitHub
parent 8486dca33a
commit 711939f71c
59 changed files with 143 additions and 226 deletions
-5
View File
@@ -336,11 +336,6 @@
"group": ["**/*.api", "**/*.electron"],
"message": "Don't directly reference imports from other platforms"
},
{
"group": ["uuid"],
"importNames": ["*"],
"message": "Use `import { v4 as uuidv4 } from 'uuid'` instead"
},
{
"group": ["**/style", "**/colors"],
"importNames": ["colors"],
+1 -2
View File
@@ -33,8 +33,7 @@
"@actual-app/core": "workspace:*",
"@actual-app/crdt": "workspace:*",
"better-sqlite3": "^12.8.0",
"compare-versions": "^6.1.1",
"uuid": "^13.0.0"
"compare-versions": "^6.1.1"
},
"devDependencies": {
"@typescript/native-preview": "^7.0.0-dev.20260404.1",
+1 -2
View File
@@ -28,8 +28,7 @@
},
"dependencies": {
"google-protobuf": "^3.21.4",
"murmurhash": "^2.0.1",
"uuid": "^13.0.0"
"murmurhash": "^2.0.1"
},
"devDependencies": {
"@types/google-protobuf": "3.15.12",
+1 -2
View File
@@ -1,5 +1,4 @@
import murmurhash from 'murmurhash';
import { v4 as uuidv4 } from 'uuid';
import type { TrieNode } from './merkle';
@@ -77,7 +76,7 @@ export function deserializeClock(clock: string): Clock {
}
export function makeClientId() {
return uuidv4().replace(/-/g, '').slice(-16);
return crypto.randomUUID().replace(/-/g, '').slice(-16);
}
const config = {
-1
View File
@@ -184,7 +184,6 @@
"sass": "^1.99.0",
"typescript-strict-plugin": "^2.4.4",
"usehooks-ts": "^3.1.1",
"uuid": "^13.0.0",
"vite": "^8.0.5",
"vite-plugin-pwa": "^1.2.0",
"vitest": "^4.1.2"
@@ -12,7 +12,6 @@ import type {
} from '@actual-app/core/types/models';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import type { QueryClient, QueryKey } from '@tanstack/react-query';
import { v4 as uuidv4 } from 'uuid';
import { sync } from '#app/appSlice';
import { useAccounts } from '#hooks/useAccounts';
@@ -44,7 +43,7 @@ const dispatchErrorNotification = (
dispatch(
addNotification({
notification: {
id: uuidv4(),
id: crypto.randomUUID(),
type: 'error',
message,
pre: error ? error.message : undefined,
@@ -9,7 +9,6 @@ import type {
import { useMutation, useQueryClient } from '@tanstack/react-query';
import type { QueryClient, QueryKey } from '@tanstack/react-query';
import type { TFunction } from 'i18next';
import { v4 as uuidv4 } from 'uuid';
import { pushModal } from '#modals/modalsSlice';
import { addNotification } from '#notifications/notificationsSlice';
@@ -32,7 +31,7 @@ function dispatchErrorNotification(
dispatch(
addNotification({
notification: {
id: uuidv4(),
id: crypto.randomUUID(),
type: 'error',
message,
pre: error ? error.message : undefined,
@@ -35,7 +35,6 @@ import type {
import { t } from 'i18next';
import debounce from 'lodash/debounce';
import isEqual from 'lodash/isEqual';
import { v4 as uuidv4 } from 'uuid';
import {
useReopenAccountMutation,
@@ -1117,7 +1116,7 @@ class AccountInternal extends PureComponent<
const [firstTransaction] = transactions;
const parentTransaction = {
id: uuidv4(),
id: crypto.randomUUID(),
is_parent: true,
cleared: transactions.every(t => !!t.cleared),
date: firstTransaction.date,
@@ -44,7 +44,6 @@ import type {
RuleEntity,
} from '@actual-app/core/types/models';
import { css } from '@emotion/css';
import { v4 as uuid } from 'uuid';
import { FinancialText } from '#components/FinancialText';
import { StatusBadge } from '#components/schedules/StatusBadge';
@@ -783,7 +782,7 @@ function StageButton({
}
function newInput(item) {
return { ...item, inputKey: uuid() };
return { ...item, inputKey: crypto.randomUUID() };
}
function ConditionsList({
@@ -821,7 +820,7 @@ function ConditionsList({
field,
op: 'is',
value: null,
inputKey: uuid(),
inputKey: crypto.randomUUID(),
});
onChangeConditions(copy);
}
@@ -1007,19 +1006,27 @@ export function RuleEditor({
}: RuleEditorProps) {
const { t } = useTranslation();
const [conditions, setConditions] = useState(
defaultRule.conditions.map(parse).map(c => ({ ...c, inputKey: uuid() })),
defaultRule.conditions
.map(parse)
.map(c => ({ ...c, inputKey: crypto.randomUUID() })),
);
const [actionSplits, setActionSplits] = useState(() => {
const parsedActions = defaultRule.actions.map(parse);
return parsedActions.reduce(
(acc, action) => {
const splitIndex = action.options?.splitIndex ?? 0;
acc[splitIndex] = acc[splitIndex] ?? { id: uuid(), actions: [] };
acc[splitIndex].actions.push({ ...action, inputKey: uuid() });
acc[splitIndex] = acc[splitIndex] ?? {
id: crypto.randomUUID(),
actions: [],
};
acc[splitIndex].actions.push({
...action,
inputKey: crypto.randomUUID(),
});
return acc;
},
// The pre-split group is always there
[{ id: uuid(), actions: [] }],
[{ id: crypto.randomUUID(), actions: [] }],
);
});
const [stage, setStage] = useState(defaultRule.stage);
@@ -1079,12 +1086,12 @@ export function RuleEditor({
function addActionToSplitAfterIndex(splitIndex, actionIndex) {
let newAction;
if (splitIndex && !actionSplits[splitIndex]?.actions?.length) {
actionSplits[splitIndex] = { id: uuid(), actions: [] };
actionSplits[splitIndex] = { id: crypto.randomUUID(), actions: [] };
newAction = {
op: 'set-split-amount',
options: { method: 'remainder', splitIndex },
value: null,
inputKey: uuid(),
inputKey: crypto.randomUUID(),
};
} else {
const fieldsArray =
@@ -1100,7 +1107,7 @@ export function RuleEditor({
op: 'set',
value: '',
options: { splitIndex },
inputKey: uuid(),
inputKey: crypto.randomUUID(),
};
}
@@ -23,7 +23,6 @@ import type {
import { fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { format as formatDate, parse as parseDate } from 'date-fns';
import { v4 as uuidv4 } from 'uuid';
import { AuthProvider } from '#auth/AuthProvider';
import { SchedulesProvider } from '#hooks/useCachedSchedules';
@@ -1087,7 +1086,7 @@ describe('Transactions', () => {
// Change the id to simulate a new transaction being added, and
// work with that one. This makes sure that the transaction table
// properly references new data.
transactions[0] = { ...transactions[0], id: uuidv4() };
transactions[0] = { ...transactions[0], id: crypto.randomUUID() };
updateProps({ transactions });
function expectErrorToNotExist(transactions: TransactionEntity[]) {
@@ -1,7 +1,6 @@
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
import { t } from 'i18next';
import { v4 as uuidv4 } from 'uuid';
import { resetApp } from '#app/appSlice';
@@ -59,7 +58,7 @@ const notificationsSlice = createSlice({
addNotification(state, action: PayloadAction<AddNotificationPayload>) {
const notification = {
...action.payload.notification,
id: action.payload.notification.id || uuidv4(),
id: action.payload.notification.id || crypto.randomUUID(),
};
if (state.notifications.find(n => n.id === notification.id)) {
@@ -69,7 +68,7 @@ const notificationsSlice = createSlice({
},
addGenericErrorNotification(state) {
const notification: NotificationWithId = {
id: uuidv4(),
id: crypto.randomUUID(),
type: 'error',
message: t(
'Something internally went wrong. You may want to restart the app if anything looks wrong. ' +
@@ -4,7 +4,6 @@ import { send } from '@actual-app/core/platform/client/connection';
import type { PayeeEntity } from '@actual-app/core/types/models';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import type { QueryClient, QueryKey } from '@tanstack/react-query';
import { v4 as uuidv4 } from 'uuid';
import { addNotification } from '#notifications/notificationsSlice';
import { useDispatch } from '#redux';
@@ -27,7 +26,7 @@ function dispatchErrorNotification(
dispatch(
addNotification({
notification: {
id: uuidv4(),
id: crypto.randomUUID(),
type: 'error',
message,
pre: error ? error.message : undefined,
@@ -13,7 +13,6 @@ import type {
} from '@actual-app/core/types/util';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import type { QueryClient, QueryKey } from '@tanstack/react-query';
import { v4 as uuidv4 } from 'uuid';
import { addNotification } from '#notifications/notificationsSlice';
import { useDispatch } from '#redux';
@@ -55,7 +54,7 @@ function dispatchErrorNotification(
dispatch(
addNotification({
notification: {
id: uuidv4(),
id: crypto.randomUUID(),
type: 'error',
message,
pre: error ? error.message : undefined,
@@ -4,7 +4,6 @@ import { send } from '@actual-app/core/platform/client/connection';
import type { TagEntity } from '@actual-app/core/types/models';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import type { QueryClient, QueryKey } from '@tanstack/react-query';
import { v4 as uuidv4 } from 'uuid';
import { addNotification } from '#notifications/notificationsSlice';
import { useDispatch } from '#redux';
@@ -26,7 +25,7 @@ function dispatchErrorNotification(
dispatch(
addNotification({
notification: {
id: uuidv4(),
id: crypto.randomUUID(),
type: 'error',
message,
pre: error ? error.message : undefined,
@@ -1,5 +1,4 @@
import type { RuleEntity } from '@actual-app/core/types/models';
import { v4 as uuid } from 'uuid';
export type ActionSplit = {
id: string;
@@ -12,7 +11,10 @@ export function groupActionsBySplitIndex(
return actions.reduce((acc, action) => {
const splitIndex =
'options' in action ? (action.options?.splitIndex ?? 0) : 0;
acc[splitIndex] = acc[splitIndex] ?? { id: uuid(), actions: [] };
acc[splitIndex] = acc[splitIndex] ?? {
id: crypto.randomUUID(),
actions: [],
};
acc[splitIndex].actions.push(action);
return acc;
}, [] as ActionSplit[]);
+4 -4
View File
@@ -1,8 +1,8 @@
import { mkdir, rm } from 'node:fs/promises';
import path from 'node:path';
import { _electron, test as base } from '@playwright/test';
import type { ElectronApplication, Page, TestInfo } from '@playwright/test';
import { ensureDir, remove } from 'fs-extra';
type ElectronFixtures = {
electronApp: ElectronApplication;
@@ -16,8 +16,8 @@ export const test = base.extend<ElectronFixtures>({
const uniqueTestId = testInfo.testId.replace(/[^\w-]/g, '-');
const testDataDir = path.join('e2e/data/', uniqueTestId);
await remove(testDataDir); // ensure any leftover test data is removed
await ensureDir(testDataDir);
await rm(testDataDir, { recursive: true, force: true }); // ensure any leftover test data is removed
await mkdir(testDataDir, { recursive: true });
const app = await _electron.launch({
args: ['.'],
@@ -34,7 +34,7 @@ export const test = base.extend<ElectronFixtures>({
// Cleanup after tests
await app.close();
await remove(testDataDir);
await rm(testDataDir, { recursive: true, force: true });
},
electronPage: async ({ electronApp }, use) => {
+9 -5
View File
@@ -1,6 +1,7 @@
import fs from 'fs';
import { createServer } from 'http';
import type { Server } from 'http';
import { cp, mkdir, rm } from 'node:fs/promises';
import path from 'path';
import type { GlobalPrefsJson } from '@actual-app/core/types/prefs';
@@ -23,7 +24,6 @@ import type {
SaveDialogOptions,
UtilityProcess,
} from 'electron';
import { copy, exists, mkdir, remove } from 'fs-extra';
import promiseRetry from 'promise-retry';
import { getMenu } from './menu';
@@ -652,13 +652,14 @@ ipcMain.handle(
);
}
if (!(await exists(newDirectory))) {
if (!fs.existsSync(newDirectory)) {
throw new Error('The destination directory does not exist');
}
await copy(currentBudgetDirectory, newDirectory, {
overwrite: true,
await cp(currentBudgetDirectory, newDirectory, {
force: true,
preserveTimestamps: true,
recursive: true,
});
} catch (error) {
logMessage(
@@ -672,7 +673,10 @@ ipcMain.handle(
await promiseRetry(
async retry => {
try {
return await remove(currentBudgetDirectory);
return await rm(currentBudgetDirectory, {
recursive: true,
force: true,
});
} catch (error) {
logMessage(
'info',
-2
View File
@@ -17,7 +17,6 @@
"dependencies": {
"@actual-app/sync-server": "workspace:*",
"better-sqlite3": "^12.8.0",
"fs-extra": "^11.3.4",
"promise-retry": "^2.0.1"
},
"devDependencies": {
@@ -27,7 +26,6 @@
"@electron/rebuild": "^4.0.3",
"@playwright/test": "1.59.1",
"@types/copyfiles": "^2",
"@types/fs-extra": "^11",
"@typescript/native-preview": "^7.0.0-dev.20260404.1",
"copyfiles": "^2.4.1",
"cross-env": "^10.1.0",
@@ -85,7 +85,6 @@ Use custom hooks from `src/hooks` instead of importing directly from react-route
### Never Use
- **`uuid` without destructuring**: Use `import { v4 as uuidv4 } from 'uuid'`
- **Direct color imports**: Use theme instead of importing colors directly
- **`@actual-app/web/*` imports in `loot-core`**: Don't import from web package in core
@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
import { DEFAULT_DASHBOARD_STATE } from '#shared/dashboard';
export default async function runMigration(db) {
@@ -27,7 +25,7 @@ export default async function runMigration(db) {
db.runQuery(
`INSERT INTO dashboard (id, type, width, height, x, y, meta) VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
uuidv4(),
crypto.randomUUID(),
widget.type,
widget.width,
widget.height,
@@ -45,9 +43,9 @@ export default async function runMigration(db) {
db.execQuery(`
INSERT INTO dashboard (id, type, width, height, x, y)
VALUES
('${uuidv4()}', 'net-worth-card', 8, 2, 0, 0),
('${uuidv4()}', 'cash-flow-card', 4, 2, 8, 0),
('${uuidv4()}', 'spending-card', 4, 2, 0, 2);
('${crypto.randomUUID()}', 'net-worth-card', 8, 2, 0, 0),
('${crypto.randomUUID()}', 'cash-flow-card', 4, 2, 8, 0),
('${crypto.randomUUID()}', 'spending-card', 4, 2, 0, 2);
`);
// Add custom reports to the dashboard
@@ -55,7 +53,7 @@ export default async function runMigration(db) {
db.runQuery(
`INSERT INTO dashboard (id, type, width, height, x, y, meta) VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
uuidv4(),
crypto.randomUUID(),
'custom-report',
4,
2,
@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
export default async function runMigration(db) {
db.transaction(() => {
// 1. Create dashboards table
@@ -16,7 +14,7 @@ export default async function runMigration(db) {
`);
// 3. Create a default dashboard
const defaultDashboardId = uuidv4();
const defaultDashboardId = crypto.randomUUID();
db.runQuery(`INSERT INTO dashboard_pages (id, name) VALUES (?, ?)`, [
defaultDashboardId,
'Main',
-1
View File
@@ -183,7 +183,6 @@
"promise-retry": "^2.0.1",
"typescript-strict-plugin": "^2.4.4",
"ua-parser-js": "^2.0.9",
"uuid": "^13.0.0",
"xml2js": "^0.6.2"
},
"devDependencies": {
+6 -7
View File
@@ -1,5 +1,4 @@
// @ts-strict-ignore
import { v4 as uuidv4 } from 'uuid';
import { addTransactions } from '#server/accounts/sync';
import { aqlQuery } from '#server/aql';
@@ -128,7 +127,7 @@ async function fillPrimaryChecking(
);
const transaction: TransactionEntity = {
id: uuidv4(),
id: crypto.randomUUID(),
amount,
payee: payee.id,
account: account.id,
@@ -145,21 +144,21 @@ async function fillPrimaryChecking(
: pickRandom(expenseCategories).id;
transaction.subtransactions = [
{
id: uuidv4(),
id: crypto.randomUUID(),
date: currentDate,
account: account.id,
amount: a,
category: pick(),
},
{
id: uuidv4(),
id: crypto.randomUUID(),
date: currentDate,
account: account.id,
amount: a,
category: pick(),
},
{
id: uuidv4(),
id: crypto.randomUUID(),
date: currentDate,
account: account.id,
amount: transaction.amount - a * 2,
@@ -429,7 +428,7 @@ async function fillOther(handlers, account, payees, groups) {
const transactions: TransactionEntity[] = [
{
id: uuidv4(),
id: crypto.randomUUID(),
amount: integer(3250, 3700) * 100 * 100,
payee: payees.find(p => p.name === 'Starting Balance').id,
account: account.id,
@@ -444,7 +443,7 @@ async function fillOther(handlers, account, payees, groups) {
const amount = integer(4, 9) * 100 * 100;
transactions.push({
id: uuidv4(),
id: crypto.randomUUID(),
amount,
payee: payee.id,
account: account.id,
+6 -8
View File
@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
import * as monthUtils from '#shared/months';
import type {
AccountEntity,
@@ -16,7 +14,7 @@ export function generateAccount(
offbudget?: boolean,
): AccountEntity {
const offlineAccount: AccountEntity = {
id: uuidv4(),
id: crypto.randomUUID(),
name,
offbudget: offbudget ? 1 : 0,
sort_order: 0,
@@ -82,7 +80,7 @@ export function generateCategory(
isIncome: boolean = false,
): CategoryEntity {
return {
id: uuidv4(),
id: crypto.randomUUID(),
name,
group,
is_income: isIncome,
@@ -96,7 +94,7 @@ export function generateCategoryGroup(
isIncome: boolean = false,
): CategoryGroupEntity {
return {
id: uuidv4(),
id: crypto.randomUUID(),
name,
is_income: isIncome,
sort_order: groupSortOrder++,
@@ -133,7 +131,7 @@ function _generateTransaction(
data: Partial<TransactionEntity> & Pick<TransactionEntity, 'account'>,
): TransactionEntity {
return {
id: data.id || uuidv4(),
id: data.id || crypto.randomUUID(),
amount: data.amount || Math.floor(random() * 10000 - 7000),
payee: data.payee || 'payed-to',
notes: 'Notes',
@@ -161,14 +159,14 @@ export function generateTransaction(
result.push(
{
id: parent.id + '/' + uuidv4(),
id: parent.id + '/' + crypto.randomUUID(),
amount: trans.amount - splitAmount,
account: parent.account,
date: parent.date,
is_child: true,
},
{
id: parent.id + '/' + uuidv4(),
id: parent.id + '/' + crypto.randomUUID(),
amount: splitAmount,
account: parent.account,
date: parent.date,
+3 -6
View File
@@ -57,12 +57,9 @@ let _id = 1;
global.resetRandomId = () => {
_id = 1;
};
vi.mock('uuid', () => ({
v4: () => {
return 'id' + _id++;
},
}));
Object.defineProperty(crypto, 'randomUUID', {
value: () => 'id' + _id++,
});
vi.mock('#server/migrate/migrations', async () => {
const realMigrations = await vi.importActual<typeof MigrationsType>(
'#server/migrate/migrations',
@@ -1,5 +1,4 @@
// @ts-strict-ignore
import { v4 as uuidv4 } from 'uuid';
import * as undo from '#platform/client/undo';
@@ -85,7 +84,7 @@ export const send: T.Send = function (
): ReturnType<T.Send> {
const [name, args, { catchErrors = false } = {}] = params;
return new Promise((resolve, reject) => {
const id = uuidv4();
const id = crypto.randomUUID();
replyHandlers.set(id, { resolve, reject });
if (socketClient) {
@@ -1,6 +1,5 @@
// @ts-strict-ignore
import { t } from 'i18next';
import { v4 as uuidv4 } from 'uuid';
import * as undo from '#platform/client/undo';
import { captureBreadcrumb, captureException } from '#platform/exceptions';
@@ -161,7 +160,7 @@ export const send: T.Send = function (
): ReturnType<T.Send> {
const [name, args, { catchErrors = false } = {}] = params;
return new Promise((resolve, reject) => {
const id = uuidv4();
const id = crypto.randomUUID();
replyHandlers.set(id, { resolve, reject });
const message = {
@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
import type { UndoState as ServerUndoState } from '#server/undo';
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
@@ -36,7 +34,7 @@ export const setUndoState = <K extends keyof Omit<UndoState, 'id'>>(
value: UndoState[K],
) => {
currentUndoState[name] = value;
currentUndoState.id = uuidv4();
currentUndoState.id = crypto.randomUUID();
};
export const getUndoState = <K extends keyof UndoState>(name: K) => {
@@ -48,7 +46,7 @@ export const getTaggedState = (id: string) => {
};
export const snapshot = () => {
const tagged = { ...currentUndoState, id: uuidv4() };
const tagged = { ...currentUndoState, id: crypto.randomUUID() };
UNDO_STATE_MRU.unshift(tagged);
UNDO_STATE_MRU = UNDO_STATE_MRU.slice(0, HISTORY_SIZE);
return tagged.id;
@@ -1,6 +1,5 @@
// @ts-strict-ignore
import SQL from 'better-sqlite3';
import { v4 as uuidv4 } from 'uuid';
import { getDataDir, readFile, removeFile } from '#platform/server/fs';
import { logger } from '#platform/server/log';
@@ -123,7 +122,7 @@ export function closeDatabase(db: SQL.Database) {
export async function exportDatabase(db: SQL.Database) {
// electron does not support better-sqlite serialize since v21
// save to file and read in the raw data.
const name = `${getDataDir()}/backup-for-export-${uuidv4()}.db`;
const name = `${getDataDir()}/backup-for-export-${crypto.randomUUID()}.db`;
await db.backup(name);
@@ -1,5 +1,4 @@
import { t } from 'i18next';
import { v4 as uuidv4 } from 'uuid';
import { captureException } from '#platform/exceptions';
import * as asyncStorage from '#platform/server/asyncStorage';
@@ -179,7 +178,7 @@ async function linkGoCardlessAccount({
account_sync_source: 'goCardless',
});
} else {
id = uuidv4();
id = crypto.randomUUID();
await db.insertWithUUID('accounts', {
id,
account_id: account.account_id,
@@ -254,7 +253,7 @@ async function linkSimpleFinAccount({
account_sync_source: 'simpleFin',
});
} else {
id = uuidv4();
id = crypto.randomUUID();
await db.insertWithUUID('accounts', {
id,
account_id: externalAccount.account_id,
@@ -328,7 +327,7 @@ async function linkPluggyAiAccount({
account_sync_source: 'pluggyai',
});
} else {
id = uuidv4();
id = crypto.randomUUID();
await db.insertWithUUID('accounts', {
id,
account_id: externalAccount.account_id,
@@ -502,7 +501,7 @@ async function closeAccount({
}
await mainApp.handlers['transaction-add']({
id: uuidv4(),
id: crypto.randomUUID(),
payee: transferPayee.id,
amount: -balance,
account: id,
@@ -1,5 +1,4 @@
// @ts-strict-ignore
import { v4 as uuidv4 } from 'uuid';
import * as db from '#server/db';
@@ -14,7 +13,7 @@ export async function findOrCreateBank(institution, requisitionId) {
}
const bankData = {
id: uuidv4(),
id: crypto.randomUUID(),
bank_id: requisitionId,
name: institution.name,
};
@@ -1,6 +1,5 @@
// @ts-strict-ignore
import * as dateFns from 'date-fns';
import { v4 as uuidv4 } from 'uuid';
import * as asyncStorage from '#platform/server/asyncStorage';
import { logger } from '#platform/server/log';
@@ -323,7 +322,7 @@ async function resolvePayee(trans, payeeName, payeesToCreate) {
return payee.id;
} else {
// Otherwise we're going to create a new one
const newPayee = { id: uuidv4(), name: payeeName };
const newPayee = { id: crypto.randomUUID(), name: payeeName };
payeesToCreate.set(payeeName.toLowerCase(), newPayee);
return newPayee.id;
}
@@ -617,7 +616,7 @@ export async function reconcileTransactions(
const { forceAddTransaction: _forceAddTransaction, ...newTrans } = trans;
const finalTransaction = {
...newTrans,
id: uuidv4(),
id: crypto.randomUUID(),
category: trans.category || null,
cleared: trans.cleared ?? defaultCleared,
};
@@ -881,7 +880,7 @@ export async function addTransactions(
const trans = await runRules(originalTrans, accountsMap);
const finalTransaction = {
id: uuidv4(),
id: crypto.randomUUID(),
...trans,
account: acctId,
cleared: trans.cleared != null ? trans.cleared : true,
@@ -1,5 +1,4 @@
// @ts-strict-ignore
import { v4 as uuidv4 } from 'uuid';
import * as db from '#server/db';
import { q } from '#shared/query';
@@ -39,14 +38,14 @@ async function insertTransactions(repeatTimes = 1) {
});
const parent = {
id: uuidv4(),
id: crypto.randomUUID(),
account: 'acct',
date: '2020-01-04',
amount: -100,
is_parent: true,
};
const parent2 = {
id: uuidv4(),
id: crypto.randomUUID(),
account: 'acct',
date: '2020-01-01',
amount: -89,
@@ -114,7 +113,7 @@ describe('compileAndRunQuery', () => {
});
it('provides named parameters and converts types', async () => {
const transId = uuidv4();
const transId = crypto.randomUUID();
await db.insertTransaction({
id: transId,
account: 'acct',
@@ -243,7 +242,7 @@ describe('compileAndRunQuery', () => {
});
it('parameters have the correct order', async () => {
const transId = uuidv4();
const transId = crypto.randomUUID();
await db.insertTransaction({
id: transId,
account: 'acct',
@@ -1,6 +1,5 @@
// @ts-strict-ignore
import AdmZip from 'adm-zip';
import { v4 as uuidv4 } from 'uuid';
import * as asyncStorage from '#platform/server/asyncStorage';
import { fetch } from '#platform/server/fetch';
@@ -287,7 +286,7 @@ export async function upload() {
}
if (!cloudFileId) {
cloudFileId = uuidv4();
cloudFileId = crypto.randomUUID();
}
let res;
@@ -1,5 +1,4 @@
import isMatch from 'lodash/isMatch';
import { v4 as uuidv4 } from 'uuid';
import { captureException } from '#platform/exceptions';
import * as fs from '#platform/server/fs';
@@ -102,7 +101,7 @@ const exportModel = {
};
async function createDashboardPage({ name }: { name: string }) {
const id = uuidv4();
const id = crypto.randomUUID();
await db.insertWithSchema('dashboard_pages', { id, name });
return id;
+2 -3
View File
@@ -9,7 +9,6 @@ import {
} from '@actual-app/crdt';
import type { Database, Statement } from '@jlongster/sql.js';
import { LRUCache } from 'lru-cache';
import { v4 as uuidv4 } from 'uuid';
import * as fs from '#platform/server/fs';
import * as sqlite from '#platform/server/sqlite';
@@ -224,7 +223,7 @@ export async function update(table, params) {
export async function insertWithUUID(table, row) {
if (!row.id) {
row = { ...row, id: uuidv4() };
row = { ...row, id: crypto.randomUUID() };
}
await insert(table, row);
@@ -293,7 +292,7 @@ export function insertWithSchema(table, row) {
// Even though `insertWithUUID` does this, we need to do it here so
// the schema validation passes
if (!row.id) {
row = { ...row, id: uuidv4() };
row = { ...row, id: crypto.randomUUID() };
}
return insertWithUUID(
@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
import * as asyncStorage from '#platform/server/asyncStorage';
import { logger } from '#platform/server/log';
import { createApp } from '#server/app';
@@ -30,7 +28,7 @@ async function keyMake({ password }: { password: string }) {
}
const salt = encryption.randomBytes(32).toString('base64');
const id = uuidv4();
const id = crypto.randomUUID();
const key = await encryption.createKey({ id, password, salt });
// Load the key
@@ -1,5 +1,4 @@
// @ts-strict-ignore
import { v4 as uuidv4 } from 'uuid';
import * as internals from '#server/encryption/encryption-internals';
@@ -12,7 +11,7 @@ class Key {
value;
constructor({ id }) {
this.id = id || uuidv4();
this.id = id || crypto.randomUUID();
}
async createFromPassword({ password, salt }) {
+1 -2
View File
@@ -1,5 +1,4 @@
// @ts-strict-ignore
import { v4 as uuidv4 } from 'uuid';
import { createApp } from '#server/app';
import * as db from '#server/db';
@@ -110,7 +109,7 @@ function filterOptionsMatch(options1, options2) {
}
async function createFilter(filter): Promise<TransactionFilterEntity['id']> {
const filterId = uuidv4();
const filterId = crypto.randomUUID();
const item = {
id: filterId,
conditions: filter.state.conditions,
@@ -1,6 +1,5 @@
// @ts-strict-ignore
import AdmZip from 'adm-zip';
import { v4 as uuidv4 } from 'uuid';
import { logger } from '#platform/server/log';
import { send } from '#server/main-app';
@@ -165,11 +164,11 @@ async function importTransactions(
// Go ahead and generate ids for all of the transactions so we can
// reliably resolve transfers
for (const transaction of data.transactions) {
entityIdMap.set(transaction.entityId, uuidv4());
entityIdMap.set(transaction.entityId, crypto.randomUUID());
if (transaction.subTransactions) {
for (const subTransaction of transaction.subTransactions) {
entityIdMap.set(subTransaction.entityId, uuidv4());
entityIdMap.set(subTransaction.entityId, crypto.randomUUID());
}
}
}
@@ -1,5 +1,4 @@
// @ts-strict-ignore
import { v4 as uuidv4 } from 'uuid';
import { logger } from '#platform/server/log';
import { send } from '#server/main-app';
@@ -600,7 +599,7 @@ async function importTransactions(
// reliably resolve transfers
// Also identify orphan transfer transactions and subtransactions.
for (const transaction of data.subtransactions) {
entityIdMap.set(transaction.id, uuidv4());
entityIdMap.set(transaction.id, crypto.randomUUID());
if (transaction.transfer_account_id) {
orphanSubtransfer.push(transaction);
@@ -609,7 +608,7 @@ async function importTransactions(
}
for (const transaction of data.transactions) {
entityIdMap.set(transaction.id, uuidv4());
entityIdMap.set(transaction.id, crypto.randomUUID());
if (
transaction.transfer_account_id &&
+1 -2
View File
@@ -1,6 +1,5 @@
// @ts-strict-ignore
import { deserializeClock, getClock } from '@actual-app/crdt';
import { v4 as uuidv4 } from 'uuid';
import { expectSnapshotWithDiffer } from '#mocks/util';
import * as connection from '#platform/server/connection';
@@ -181,7 +180,7 @@ describe('Budget', () => {
// budgets for the earlier months
db.runQuery("INSERT INTO accounts (id, name) VALUES ('one', 'boa')");
await runHandler(handlers['transaction-add'], {
id: uuidv4(),
id: crypto.randomUUID(),
date: '2016-05-06',
amount: 50,
account: 'one',
+1 -3
View File
@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
import { createApp } from '#server/app';
import { aqlQuery } from '#server/aql';
import * as db from '#server/db';
@@ -130,7 +128,7 @@ async function reportNameExists(
}
async function createReport(report: CustomReportEntity) {
const reportId = uuidv4();
const reportId = crypto.randomUUID();
const item: CustomReportEntity = {
...report,
id: reportId,
@@ -1,6 +1,5 @@
// @ts-strict-ignore
import * as d from 'date-fns';
import { v4 as uuidv4 } from 'uuid';
import { captureBreadcrumb } from '#platform/exceptions';
import * as connection from '#platform/server/connection';
@@ -255,7 +254,7 @@ export async function createSchedule({
schedule = null,
conditions = [],
} = {}): Promise<ScheduleEntity['id']> {
const scheduleId = schedule?.id || uuidv4();
const scheduleId = schedule?.id || crypto.randomUUID();
const { date: dateCond } = extractScheduleConds(conditions);
if (dateCond == null) {
@@ -1,6 +1,5 @@
// @ts-strict-ignore
import * as d from 'date-fns';
import { v4 as uuidv4 } from 'uuid';
import { aqlQuery } from '#server/aql';
import * as db from '#server/db';
@@ -360,7 +359,7 @@ export async function findSchedules() {
// Convert to schedule and return it
return {
id: uuidv4(),
id: crypto.randomUUID(),
account: winner.account,
payee: winner.payee,
date: winner.date,
@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
import * as fs from '#platform/server/fs';
import { handlers } from '#server/main';
@@ -39,7 +37,10 @@ export async function validateBudgetName(
}
export async function idFromBudgetName(name: string): Promise<string> {
let id = name.replace(/( |[^A-Za-z0-9])/g, '-') + '-' + uuidv4().slice(0, 7);
let id =
name.replace(/( |[^A-Za-z0-9])/g, '-') +
'-' +
crypto.randomUUID().slice(0, 7);
// Make sure the id is unique. There's a chance one could already
// exist (although very unlikely now that we append unique
@@ -1,5 +1,4 @@
// @ts-strict-ignore
import { v4 as uuidv4 } from 'uuid';
import type { TransactionEntity } from '#types/models';
@@ -14,7 +13,7 @@ import {
function makeTransaction(data: Partial<TransactionEntity>): TransactionEntity {
return {
id: uuidv4(),
id: crypto.randomUUID(),
amount: 2422,
date: '2020-01-05',
account: 'acc-id-1',
@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
import { logger } from '#platform/server/log';
import type { TransactionEntity } from '#types/models';
@@ -41,7 +39,7 @@ export function makeChild<T extends GenericTransactionEntity>(
...data,
category: 'category' in data ? data.category : parent.category,
payee: 'payee' in data ? data.payee : parent.payee,
id: 'id' in data ? data.id : prefix + uuidv4(),
id: 'id' in data ? data.id : prefix + crypto.randomUUID(),
account: parent.account,
date: parent.date,
cleared: parent.cleared != null ? parent.cleared : null,
@@ -359,7 +357,7 @@ export function realizeTempTransactions(
): TransactionEntity[] {
const parent = {
...transactions.find(t => !t.is_child),
id: uuidv4(),
id: crypto.randomUUID(),
sort_order: Date.now(),
} as TransactionEntity;
const children = transactions.filter(t => t.is_child);
@@ -369,7 +367,7 @@ export function realizeTempTransactions(
child =>
({
...child,
id: uuidv4(),
id: crypto.randomUUID(),
parent_id: parent.id,
}) satisfies TransactionEntity,
),
@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
import { getAccountDb } from '../src/account-db';
export const up = async function () {
@@ -38,7 +36,7 @@ export const up = async function () {
`,
);
const userId = uuidv4();
const userId = crypto.randomUUID();
accountDb.mutate(
'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, 1, 1, ?)',
[userId, '', '', 'ADMIN'],
-1
View File
@@ -92,7 +92,6 @@
"migrate": "^2.1.0",
"openid-client": "^5.7.1",
"pluggy-sdk": "^0.83.0",
"uuid": "^13.0.0",
"winston": "^3.19.0"
},
"devDependencies": {
+2 -3
View File
@@ -1,5 +1,4 @@
import { custom, generators, Issuer } from 'openid-client';
import { v4 as uuidv4 } from 'uuid';
import {
clearExpiredSessions,
@@ -254,7 +253,7 @@ export async function loginWithOpenIdFinalize(body) {
(countUsersWithUserName === 0 ||
config.get('userCreationMode') === 'login')
) {
userId = uuidv4();
userId = crypto.randomUUID();
accountDb.mutate(
'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, 1, ?, ?)',
[
@@ -303,7 +302,7 @@ export async function loginWithOpenIdFinalize(body) {
}
}
const token = uuidv4();
const token = crypto.randomUUID();
let expiration;
if (config.get('token_expiration') === 'openid-provider') {
@@ -1,5 +1,4 @@
import * as bcrypt from 'bcrypt';
import { v4 as uuidv4 } from 'uuid';
import { clearExpiredSessions, getAccountDb } from '#account-db';
import { config } from '#load-config';
@@ -58,14 +57,14 @@ export function loginWithPassword(password) {
['password'],
);
const token = sessionRow ? sessionRow.token : uuidv4();
const token = sessionRow ? sessionRow.token : crypto.randomUUID();
const { totalOfUsers } = accountDb.first(
'SELECT count(*) as totalOfUsers FROM users',
);
let userId = null;
if (totalOfUsers === 0) {
userId = uuidv4();
userId = crypto.randomUUID();
accountDb.mutate(
'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, 1, 1, ?)',
[userId, '', '', 'ADMIN'],
+5 -6
View File
@@ -1,5 +1,4 @@
import request from 'supertest';
import { v4 as uuidv4 } from 'uuid';
import { getAccountDb, getLoginMethod, getServerPrefs } from './account-db';
import { bootstrapPassword } from './accounts/password';
@@ -28,7 +27,7 @@ const createSession = (userId, sessionToken, authMethod = null) => {
);
};
const generateSessionToken = () => `token-${uuidv4()}`;
const generateSessionToken = () => `token-${crypto.randomUUID()}`;
const clearServerPrefs = () => {
getAccountDb().mutate('DELETE FROM server_prefs');
@@ -98,8 +97,8 @@ describe('/change-password', () => {
basicPasswordToken;
beforeEach(() => {
adminUserId = uuidv4();
basicUserId = uuidv4();
adminUserId = crypto.randomUUID();
basicUserId = crypto.randomUUID();
adminPasswordToken = generateSessionToken();
adminOpenidToken = generateSessionToken();
basicPasswordToken = generateSessionToken();
@@ -261,8 +260,8 @@ describe('/server-prefs', () => {
let adminUserId, basicUserId, adminSessionToken, basicSessionToken;
beforeEach(() => {
adminUserId = uuidv4();
basicUserId = uuidv4();
adminUserId = crypto.randomUUID();
basicUserId = crypto.randomUUID();
adminSessionToken = generateSessionToken();
basicSessionToken = generateSessionToken();
+1 -2
View File
@@ -1,5 +1,4 @@
import express from 'express';
import { v4 as uuidv4 } from 'uuid';
import { isAdmin } from './account-db';
import * as UserService from './services/user-service';
@@ -78,7 +77,7 @@ app.post('/users', validateSessionMiddleware, async (req, res) => {
return;
}
const userId = uuidv4();
const userId = crypto.randomUUID();
UserService.insertUser(
userId,
userName,
+15 -16
View File
@@ -1,5 +1,4 @@
import request from 'supertest';
import { v4 as uuidv4 } from 'uuid';
import { getAccountDb } from './account-db';
import { handlers as app } from './app-admin';
@@ -27,13 +26,13 @@ const createSession = (userId, sessionToken) => {
);
};
const generateSessionToken = () => `token-${uuidv4()}`;
const generateSessionToken = () => `token-${crypto.randomUUID()}`;
describe('/admin', () => {
describe('/owner-created', () => {
it('should return 200 and true if an owner user is created', async () => {
const sessionToken = generateSessionToken();
const adminId = uuidv4();
const adminId = crypto.randomUUID();
createUser(adminId, 'admin', ADMIN_ROLE, 1);
createSession(adminId, sessionToken);
@@ -51,8 +50,8 @@ describe('/admin', () => {
let sessionUserId, testUserId, sessionToken;
beforeEach(() => {
sessionUserId = uuidv4();
testUserId = uuidv4();
sessionUserId = crypto.randomUUID();
testUserId = crypto.randomUUID();
sessionToken = generateSessionToken();
createUser(sessionUserId, 'sessionUser', ADMIN_ROLE);
@@ -81,7 +80,7 @@ describe('/admin', () => {
let duplicateUserId;
beforeEach(() => {
sessionUserId = uuidv4();
sessionUserId = crypto.randomUUID();
sessionToken = generateSessionToken();
createUser(sessionUserId, 'sessionUser', ADMIN_ROLE);
createSession(sessionUserId, sessionToken);
@@ -152,8 +151,8 @@ describe('/admin', () => {
let sessionUserId, testUserId, sessionToken;
beforeEach(() => {
sessionUserId = uuidv4();
testUserId = uuidv4();
sessionUserId = crypto.randomUUID();
testUserId = crypto.randomUUID();
sessionToken = generateSessionToken();
createUser(sessionUserId, 'sessionUser', ADMIN_ROLE);
@@ -209,8 +208,8 @@ describe('/admin', () => {
let sessionUserId, testUserId, sessionToken;
beforeEach(() => {
sessionUserId = uuidv4();
testUserId = uuidv4();
sessionUserId = crypto.randomUUID();
testUserId = crypto.randomUUID();
sessionToken = generateSessionToken();
createUser(sessionUserId, 'sessionUser', ADMIN_ROLE);
@@ -260,9 +259,9 @@ describe('/admin', () => {
let sessionUserId, testUserId, fileId, sessionToken;
beforeEach(() => {
sessionUserId = uuidv4();
testUserId = uuidv4();
fileId = uuidv4();
sessionUserId = crypto.randomUUID();
testUserId = crypto.randomUUID();
fileId = crypto.randomUUID();
sessionToken = generateSessionToken();
createUser(sessionUserId, 'sessionUser', ADMIN_ROLE);
@@ -321,9 +320,9 @@ describe('/admin', () => {
let sessionUserId, testUserId, fileId, sessionToken;
beforeEach(() => {
sessionUserId = uuidv4();
testUserId = uuidv4();
fileId = uuidv4();
sessionUserId = crypto.randomUUID();
testUserId = crypto.randomUUID();
fileId = crypto.randomUUID();
sessionToken = generateSessionToken();
createUser(sessionUserId, 'sessionUser', ADMIN_ROLE);
@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
import {
BankFactory,
isSpecialContinuousAccessBank,
@@ -289,7 +287,7 @@ export const goCardlessService = {
const body = {
redirectUrl: host + '/gocardless/link',
institutionId,
referenceId: uuidv4(),
referenceId: crypto.randomUUID(),
accessValidForDays: institution.max_access_valid_for_days,
maxHistoricalDays: isSpecialContinuousAccessBank(institutionId)
? 90
+2 -3
View File
@@ -5,7 +5,6 @@ import { resolve } from 'node:path';
import { SyncProtoBuf } from '@actual-app/crdt';
import express from 'express';
import { v4 as uuidv4 } from 'uuid';
import { getAccountDb, isAdmin } from './account-db';
import { FileNotFound } from './app-sync/errors';
@@ -312,7 +311,7 @@ app.post('/upload-user-file', async (req, res) => {
if (!currentFile) {
// it's new
groupId = uuidv4();
groupId = crypto.randomUUID();
filesService.set(
new File({
@@ -335,7 +334,7 @@ app.post('/upload-user-file', async (req, res) => {
if (!groupId) {
// sync state was reset, create new group
groupId = uuidv4();
groupId = crypto.randomUUID();
filesService.update(fileId, new FileUpdate({ groupId }));
}
+6
View File
@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [matt-fidd]
---
Remove some dependencies that can now be replaced by Node.js builtins
+1 -36
View File
@@ -30,7 +30,6 @@ __metadata:
compare-versions: "npm:^6.1.1"
rollup-plugin-visualizer: "npm:^7.0.1"
typescript-strict-plugin: "npm:^2.4.4"
uuid: "npm:^13.0.0"
vite: "npm:^8.0.5"
vite-plugin-peggy-loader: "npm:^2.0.1"
vitest: "npm:^4.1.2"
@@ -145,7 +144,6 @@ __metadata:
typescript-strict-plugin: "npm:^2.4.4"
ua-parser-js: "npm:^2.0.9"
util: "npm:^0.12.5"
uuid: "npm:^13.0.0"
vite: "npm:^8.0.5"
vite-plugin-node-polyfills: "npm:^0.26.0"
vite-plugin-peggy-loader: "npm:^2.0.1"
@@ -176,7 +174,6 @@ __metadata:
murmurhash: "npm:^2.0.1"
protoc-gen-js: "npm:3.21.4-4"
ts-protoc-gen: "npm:0.15.0"
uuid: "npm:^13.0.0"
vitest: "npm:^4.1.2"
languageName: unknown
linkType: soft
@@ -213,7 +210,6 @@ __metadata:
pluggy-sdk: "npm:^0.83.0"
supertest: "npm:^7.2.2"
typescript-strict-plugin: "npm:^2.4.4"
uuid: "npm:^13.0.0"
vitest: "npm:^4.1.2"
winston: "npm:^3.19.0"
bin:
@@ -302,7 +298,6 @@ __metadata:
sass: "npm:^1.99.0"
typescript-strict-plugin: "npm:^2.4.4"
usehooks-ts: "npm:^3.1.1"
uuid: "npm:^13.0.0"
vite: "npm:^8.0.5"
vite-plugin-pwa: "npm:^1.2.0"
vitest: "npm:^4.1.2"
@@ -9710,16 +9705,6 @@ __metadata:
languageName: node
linkType: hard
"@types/fs-extra@npm:^11":
version: 11.0.4
resolution: "@types/fs-extra@npm:11.0.4"
dependencies:
"@types/jsonfile": "npm:*"
"@types/node": "npm:*"
checksum: 10/acc4c1eb0cde7b1f23f3fe6eb080a14832d8fa9dc1761aa444c5e2f0f6b6fa657ed46ebae32fb580a6700fc921b6165ce8ac3e3ba030c3dd15f10ad4dd4cae98
languageName: node
linkType: hard
"@types/geojson@npm:*":
version: 7946.0.16
resolution: "@types/geojson@npm:7946.0.16"
@@ -9829,15 +9814,6 @@ __metadata:
languageName: node
linkType: hard
"@types/jsonfile@npm:*":
version: 6.1.4
resolution: "@types/jsonfile@npm:6.1.4"
dependencies:
"@types/node": "npm:*"
checksum: 10/309fda20eb5f1cf68f2df28931afdf189c5e7e6bec64ac783ce737bb98908d57f6f58757ad5da9be37b815645a6f914e2d4f3ac66c574b8fe1ba6616284d0e97
languageName: node
linkType: hard
"@types/keyv@npm:^3.1.4":
version: 3.1.4
resolution: "@types/keyv@npm:3.1.4"
@@ -14500,14 +14476,12 @@ __metadata:
"@electron/rebuild": "npm:^4.0.3"
"@playwright/test": "npm:1.59.1"
"@types/copyfiles": "npm:^2"
"@types/fs-extra": "npm:^11"
"@typescript/native-preview": "npm:^7.0.0-dev.20260404.1"
better-sqlite3: "npm:^12.8.0"
copyfiles: "npm:^2.4.1"
cross-env: "npm:^10.1.0"
electron: "npm:39.8.5"
electron-builder: "npm:26.8.1"
fs-extra: "npm:^11.3.4"
promise-retry: "npm:^2.0.1"
typescript-strict-plugin: "npm:^2.4.4"
languageName: unknown
@@ -16545,7 +16519,7 @@ __metadata:
languageName: node
linkType: hard
"fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0, fs-extra@npm:^11.3.4":
"fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0":
version: 11.3.4
resolution: "fs-extra@npm:11.3.4"
dependencies:
@@ -28079,15 +28053,6 @@ __metadata:
languageName: node
linkType: hard
"uuid@npm:^13.0.0":
version: 13.0.0
resolution: "uuid@npm:13.0.0"
bin:
uuid: dist-node/bin/uuid
checksum: 10/2742b24d1e00257e60612572e4d28679423469998cafbaf1fe9f1482e3edf9c40754b31bfdb3d08d71b29239f227a304588f75210b3b48f2609f0673f1feccef
languageName: node
linkType: hard
"uuid@npm:^8.3.2":
version: 8.3.2
resolution: "uuid@npm:8.3.2"