define workspace

This commit is contained in:
vorotamoroz
2026-06-17 04:01:45 +01:00
parent 18a59219f7
commit 5a35b71339
17 changed files with 1453 additions and 710 deletions
+35 -2
View File
@@ -19,11 +19,15 @@ const packageJson = JSON.parse(fs.readFileSync("./package.json") + "");
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
const PATHS_TEST_INSTALL = process.env?.PATHS_TEST_INSTALL || "";
const PATH_TEST_INSTALL = PATHS_TEST_INSTALL.split(path.delimiter).map(p => p.trim()).filter(p => p.length);
const PATH_TEST_INSTALL = PATHS_TEST_INSTALL.split(path.delimiter)
.map((p) => p.trim())
.filter((p) => p.length);
if (PATH_TEST_INSTALL) {
console.log(`Built files will be copied to ${PATH_TEST_INSTALL}`);
} else {
console.log("Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows).");
console.log(
"Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows)."
);
}
const moduleAliasPlugin = {
@@ -66,6 +70,34 @@ const moduleAliasPlugin = {
},
};
const removePragmaCommentsPlugin = {
name: "remove-pragma-comments",
setup(build) {
// Filter target extensions (e.g., JavaScript and TypeScript)
build.onLoad({ filter: /\.[jt]s?$/ }, async (args) => {
const source = await fs.promises.readFile(args.path, "utf8");
// Regex targeting both single-line and multi-line comments
// This regex looks for:
// - /* eslint ... */ (multi-line)
// const esLintPragmaRegexBlock = /\/\*[\s\S]*?eslint[\s\S]*?\*\/|([^\\:]|^)\/\/.*eslint.*$/gm;
// - // eslint-disable-next-line
let cleanedSource = source;
const tsIgnoreRegex = /\/\*\s*@ts-ignore\s*\*\/|([^\\:]|^)\/\/.*?@ts-ignore.*$/gm;
const esLintPragmaRegexLine = /([^\\:]|^)\/\/.*?eslint-.*$/gm;
const exps = [tsIgnoreRegex, esLintPragmaRegexLine];
for (const exp of exps) {
cleanedSource = cleanedSource.replace(exp, "$1");
}
return {
contents: cleanedSource,
loader: args.path.endsWith("ts") ? "ts" : "js",
};
});
},
};
/** @type esbuild.Plugin[] */
const plugins = [
{
@@ -177,6 +209,7 @@ const context = await esbuild.context({
preprocess: sveltePreprocess(),
compilerOptions: { css: "injected", preserveComments: false },
}),
removePragmaCommentsPlugin,
...plugins,
],
});
+143
View File
@@ -0,0 +1,143 @@
const restrictedGlobalsOptions = [
{
name: "app",
message: "Avoid using the global app object. Instead use the reference provided by your plugin instance.",
},
"warn",
{
name: "fetch",
message: "Use the built-in `requestUrl` function instead of `fetch` for network requests in Obsidian.",
},
{
name: "localStorage",
message:
"Prefer `App#saveLocalStorage` / `App#loadLocalStorage` functions to write / read localStorage data that's unique to a vault.",
},
];
const restrictedImportsOptions = [
{
name: "axios",
message: "Use the built-in `requestUrl` function instead of `axios`.",
},
{
name: "superagent",
message: "Use the built-in `requestUrl` function instead of `superagent`.",
},
{
name: "got",
message: "Use the built-in `requestUrl` function instead of `got`.",
},
{
name: "ofetch",
message: "Use the built-in `requestUrl` function instead of `ofetch`.",
},
{
name: "ky",
message: "Use the built-in `requestUrl` function instead of `ky`.",
},
{
name: "node-fetch",
message: "Use the built-in `requestUrl` function instead of `node-fetch`.",
},
{
name: "moment",
message: "The 'moment' package is bundled with Obsidian. Please import it from 'obsidian' instead.",
},
];
const warnWhileDev = "off";
/**
* @type {import("eslint").Linter.RulesRecord}
*/
export const baseRules = {
// -- Base rules (turned off in favour of TS specific versions or explicitly disabled).
"no-unused-vars": "off",
"no-unused-labels": "off",
"no-prototype-builtins": "off",
"require-await": "off",
// -- TypeScript specific rules (Gradual adoption of stricter rules, currently set to 'warn' for a while).
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-redundant-type-constituents": "warn",
// -- TypeScript specific rules
// @typescript-eslint/no-unsafe-* rules and @typescript-eslint/no-explicit-any:
// This project contains a lot of library-sh code where the use of `any` is often necessary and justified.
// Rules is now set to 'off' for a while.
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
// -- Reasonable rules.
"@typescript-eslint/no-deprecated": warnWhileDev,
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/require-await": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
// -- General rules
"no-async-promise-executor": warnWhileDev,
"no-constant-condition": ["error", { checkLoops: false }],
// -- Disabled rules
// no-undef: This option breaks the global declarations for the library files and is not worth the effort to fix at this time.
"no-undef": "off",
};
/**
* @type {import("eslint").Linter.RulesRecord}
*/
export const obsidianRules = {
// -- Obsidian rules
// obsidianmd/no-unsupported-api: usually this project checks for API support at runtime, so this rule is not critical but can be helpful to catch potential issues.
"obsidianmd/no-unsupported-api": warnWhileDev,
// -- Plugin specific overrides
"obsidianmd/rule-custom-message": "off",
"obsidianmd/ui/sentence-case": "off",
"obsidianmd/no-plugin-as-component": "off",
// -- Temporary overrides for migration
"obsidianmd/no-static-styles-assignment": "off",
};
/**
* @type {(base:string) => import("eslint").Linter.RulesRecord}
*/
export const ImportAliasRules = (base) => ({
"@dword-design/import-alias/prefer-alias": [
"error",
{
aliasForSubpaths: true,
alias: {
"@": `${base}/src`,
"@lib": `${base}/src/lib/src`,
},
},
],
});
/**
* @type {import("eslint").Linter.RulesRecord}
*/
export const CommunityReviewRecommendedRules = {
"no-unused-vars": "off",
"no-prototype-bultins": "off",
"no-self-compare": "warn",
"no-eval": "error",
"no-implied-eval": "error",
"prefer-const": "off",
"no-implicit-globals": "error",
"no-console": "off", // overridden by obsidianmd/rule-custom-message
"no-restricted-globals": ["error", ...restrictedGlobalsOptions],
"no-restricted-imports": ["error", ...restrictedImportsOptions],
"no-alert": "error",
"no-undef": "error",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-deprecated": "error",
"@typescript-eslint/no-unused-vars": ["warn", { args: "none" }],
"@typescript-eslint/require-await": "off",
"@typescript-eslint/no-explicit-any": ["error", { fixToUnknown: true }],
// "import/no-nodejs-modules": "off",
// "import/no-extraneous-dependencies": "error",
};
+17 -49
View File
@@ -4,6 +4,8 @@ import globals from "globals";
import { defineConfig, globalIgnores } from "eslint/config";
import * as sveltePlugin from "eslint-plugin-svelte";
import svelteParser from "svelte-eslint-parser";
import importAlias from "@dword-design/eslint-plugin-import-alias";
import { baseRules, ImportAliasRules, obsidianRules } from "./eslint.config.common.mjs";
const warnWhileDev = "off"; // Change to "warn" to enable warnings for rules that are currently disabled.
export default defineConfig([
globalIgnores([
@@ -18,11 +20,11 @@ export default defineConfig([
"**/*.json",
"**/.eslintrc.js.bak",
// Files from linked dependencies (those files should not exist for most people).
"modules/octagonal-wheels/dist/**/*",
"modules/octagonal-wheels/dist",
// Sub-projects (Exclude from root linting as they have different environments)
"src/apps/**/*",
"utils/**/*",
"src/apps",
"utils",
// Specific exclusions from common library (src/lib)
"src/lib/coverage",
@@ -54,6 +56,7 @@ export default defineConfig([
]),
...sveltePlugin.configs["flat/base"],
...obsidianmd.configs.recommended,
importAlias.configs.recommended,
{
files: ["**/*.ts"],
// ignores:["src/lib/**/*.ts"], // Exclude library files from root linting (they have different environments and rules).
@@ -62,64 +65,29 @@ export default defineConfig([
parser: tsParser,
parserOptions: {
project: "./tsconfig.json",
rootDir: "./",
},
},
linterOptions:{
linterOptions: {
reportUnusedDisableDirectives: false,
},
rules: {
// -- Base rules (turned off in favour of TS specific versions or explicitly disabled).
"no-unused-vars": "off",
"no-unused-labels": "off",
"no-prototype-builtins": "off",
"require-await": "off",
// -- TypeScript specific rules
// @typescript-eslint/no-unsafe-* rules and @typescript-eslint/no-explicit-any:
// This project contains a lot of library-sh code where the use of `any` is often necessary and justified.
// Rules is now set to 'off' for a while.
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
// -- Reasonable rules.
"@typescript-eslint/no-deprecated": warnWhileDev,
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/require-await": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
// -- Obsidian rules
// obsidianmd/no-unsupported-api: usually this project checks for API support at runtime, so this rule is not critical but can be helpful to catch potential issues.
"obsidianmd/no-unsupported-api": warnWhileDev,
// -- General rules
"no-async-promise-executor": warnWhileDev,
"no-constant-condition": ["error", { checkLoops: false }],
// -- Disabled rules
// no-undef: This option breaks the global declarations for the library files and is not worth the effort to fix at this time.
"no-undef": "off",
// -- Plugin specific overrides
"obsidianmd/rule-custom-message": "off",
"obsidianmd/ui/sentence-case": "off",
"obsidianmd/no-plugin-as-component": "off",
// -- Temporary overrides for migration
"obsidianmd/no-static-styles-assignment": "off",
...baseRules,
...obsidianRules,
// -- Project specific rules
...ImportAliasRules("."),
},
},
{
files: ["**/*.svelte"],
languageOptions: {
globals: { ...globals.browser, PouchDB: "readonly" },
parser: svelteParser,
parserOptions: {
parser: tsParser,
extraFileExtensions: [".svelte"],
project: "./tsconfig.json",
rootDir: "./",
},
},
rules: {
@@ -127,8 +95,8 @@ export default defineConfig([
// Svelte template's declarations have a lot of false positives and the rule is not worth the effort to fix at this time.
// it may improve in the future with some options as like ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],]
"no-unused-vars": "off",
"obsidianmd/no-plugin-as-component": "off",
"obsidianmd/ui/sentence-case": "off",
...obsidianRules,
...ImportAliasRules("."),
},
},
]);
+643 -639
View File
File diff suppressed because it is too large Load Diff
+13 -8
View File
@@ -57,13 +57,14 @@
"test:docker-all:stop": "npm run test:docker-all:down",
"test:full": "npm run test:docker-all:start && vitest run --coverage && npm run test:docker-all:stop",
"test:p2p": "bash test/suitep2p/run-p2p-tests.sh",
"version": "node version-bump.mjs && git add manifest.json versions.json"
"update-workspaces": "node update-workspaces.mjs",
"version": "node version-bump.mjs && node update-workspaces.mjs && git add manifest.json versions.json src/apps/cli/package.json src/apps/webpeer/package.json src/apps/webapp/package.json"
},
"keywords": [],
"author": "vorotamoroz",
"license": "MIT",
"devDependencies": {
"@chialab/esbuild-plugin-worker": "^0.19.0",
"@dword-design/eslint-plugin-import-alias": "^8.1.8",
"@eslint/js": "^9.39.3",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tsconfig/svelte": "^5.0.8",
@@ -96,7 +97,6 @@
"globals": "^14.0.0",
"playwright": "^1.58.2",
"postcss": "^8.5.6",
"postcss-load-config": "^6.0.1",
"pouchdb-adapter-http": "^9.0.0",
"pouchdb-adapter-idb": "^9.0.0",
"pouchdb-adapter-indexeddb": "^9.0.0",
@@ -111,14 +111,15 @@
"prettier": "3.8.1",
"rollup-plugin-copy": "^3.5.0",
"svelte": "5.41.1",
"svelte-check": "^4.4.3",
"svelte-check": "^4.6.0",
"svelte-eslint-parser": "^1.8.0",
"svelte-preprocess": "^6.0.3",
"terser": "^5.39.0",
"tinyglobby": "^0.2.15",
"transform-pouch": "^2.0.0",
"tslib": "^2.8.1",
"tsx": "^4.21.0",
"typescript": "5.9.3",
"typescript-eslint": "^8.61.0",
"vite": "^7.3.1",
"vite-plugin-istanbul": "^8.0.0",
"vitest": "^4.1.8",
@@ -132,15 +133,14 @@
"@smithy/middleware-apply-body-checksum": "^4.3.9",
"@smithy/protocol-http": "^5.3.9",
"@smithy/querystring-builder": "^4.2.9",
"@smithy/types": "^4.14.3",
"@smithy/util-retry": "^4.4.5",
"@trystero-p2p/nostr": "^0.24.0",
"chokidar": "^4.0.0",
"commander": "^14.0.3",
"diff-match-patch": "^1.0.5",
"fflate": "^0.8.2",
"idb": "^8.0.3",
"markdown-it": "^14.1.1",
"micromatch": "^4.0.0",
"minimatch": "^10.2.2",
"obsidian": "^1.12.3",
"octagonal-wheels": "^0.1.46",
@@ -148,5 +148,10 @@
"qrcode-generator": "^1.4.4",
"werift": "^0.23.0",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
}
},
"workspaces": [
"src/apps/cli",
"src/apps/webpeer",
"src/apps/webapp"
]
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "self-hosted-livesync-cli",
"private": true,
"version": "0.0.0",
"version": "0.25.76-cli",
"main": "dist/index.cjs",
"type": "module",
"scripts": {
+4 -4
View File
@@ -15,18 +15,18 @@
"noEmit": true,
/* Linting */
"strict": false,
"strict": true,
// "noImplicitAny": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
// "rootDir": "../../../",
/* Path mapping */
"baseUrl": ".",
"paths": {
"@/*": ["../../*"],
"@lib/*": ["../../lib/src/*"]
}
},
"include": ["*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "dist"]
"exclude": ["node_modules", "dist", "test", "testdeno"]
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "livesync-webapp",
"private": true,
"version": "0.0.1",
"version": "0.25.76-webapp",
"type": "module",
"description": "Browser-based Self-hosted LiveSync using FileSystem API",
"scripts": {
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "webpeer",
"private": true,
"version": "0.0.0",
"version": "0.25.76-webpeer",
"type": "module",
"scripts": {
"dev": "vite",
@@ -17,7 +17,7 @@
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tsconfig/svelte": "^5.0.8",
"svelte": "5.41.1",
"svelte-check": "^443.3",
"svelte-check": "^4.6.0",
"typescript": "5.9.3",
"vite": "^7.3.1"
},
+4 -2
View File
@@ -1,5 +1,5 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"extends": "../../../tsconfig.json",
"compilerOptions": {
"sourceRoot": "../",
"target": "ESNext",
@@ -15,11 +15,13 @@
"allowJs": true,
"checkJs": true,
"isolatedModules": true,
"allowImportingTsExtensions": true,
"moduleDetection": "force",
"paths": {
"@/*": ["../../*"],
"@lib/*": ["../../lib/src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"exclude": ["node_modules", "dist"]
}
+2 -2
View File
@@ -23,6 +23,6 @@
"@lib/*": ["./src/lib/src/*"]
}
},
"include": ["**/*.ts", "test/**/*.test.ts", "**/*.unit.spec.ts"],
"exclude": ["pouchdb-browser-webpack", "utils", "src/apps", "src/**/*.test.ts", "**/_test/**"]
"include": ["**/*.ts", "test/**/*.test.ts", "**/*.unit.spec.ts", "**/*.svelte"],
"exclude": ["pouchdb-browser-webpack", "utils", "src/apps", "src/**/*.test.ts", "**/_test/**", "utilsdeno"]
}
+111
View File
@@ -0,0 +1,111 @@
import { readFileSync, writeFileSync, statSync, readdirSync } from 'fs';
import { join, basename, resolve } from 'path';
// Read the root package.json file.
const rootPackagePath = resolve('package.json');
let rootPackage;
try {
rootPackage = JSON.parse(readFileSync(rootPackagePath, 'utf8'));
} catch (error) {
console.error('Failed to read the root package.json:', error);
process.exit(1);
}
const mainVersion = rootPackage.version;
if (!mainVersion) {
console.error('No version found in the root package.json.');
process.exit(1);
}
const workspaces = rootPackage.workspaces;
if (!workspaces || !Array.isArray(workspaces)) {
console.error('No workspaces array defined in the root package.json.');
process.exit(1);
}
// Collect all root dependencies for version matching.
const rootDeps = {
...(rootPackage.dependencies || {}),
...(rootPackage.devDependencies || {}),
...(rootPackage.peerDependencies || {})
};
// Helper function to resolve glob patterns (for example, src/apps/*)
function resolveWorkspaceDirs(patterns) {
const dirs = [];
for (const pattern of patterns) {
if (pattern.endsWith('/*')) {
const baseDir = pattern.slice(0, -2);
try {
const subDirs = readdirSync(baseDir);
for (const subDir of subDirs) {
const fullPath = join(baseDir, subDir);
if (statSync(fullPath).isDirectory()) {
dirs.push(fullPath);
}
}
} catch (error) {
console.warn(`Could not read the directory for pattern '${pattern}':`, error.message);
}
} else {
dirs.push(pattern);
}
}
return dirs;
}
const workspaceDirs = resolveWorkspaceDirs(workspaces);
for (const dir of workspaceDirs) {
const pkgJsonPath = join(dir, 'package.json');
let pkg;
try {
pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
} catch (error) {
// Skip the directory if package.json does not exist.
continue;
}
const workspaceName = basename(dir);
const targetVersion = `${mainVersion}-${workspaceName}`;
console.log(`Updating ${pkg.name || dir}:`);
console.log(` Version: ${pkg.version} -> ${targetVersion}`);
pkg.version = targetVersion;
// Synchronise dependencies.
if (pkg.dependencies) {
for (const dep of Object.keys(pkg.dependencies)) {
if (dep in rootDeps) {
const oldVer = pkg.dependencies[dep];
const newVer = rootDeps[dep];
if (oldVer !== newVer) {
console.log(` Dependency '${dep}': ${oldVer} -> ${newVer}`);
pkg.dependencies[dep] = newVer;
}
}
}
}
// Synchronise devDependencies.
if (pkg.devDependencies) {
for (const dep of Object.keys(pkg.devDependencies)) {
if (dep in rootDeps) {
const oldVer = pkg.devDependencies[dep];
const newVer = rootDeps[dep];
if (oldVer !== newVer) {
console.log(` DevDependency '${dep}': ${oldVer} -> ${newVer}`);
pkg.devDependencies[dep] = newVer;
}
}
}
}
// Write back the modified package.json file.
try {
writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 4) + '\n', 'utf8');
console.log(` Successfully updated ${pkgJsonPath}`);
} catch (error) {
console.error(` Failed to write ${pkgJsonPath}:`, error);
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"tasks": {
"dev": "deno run --watch --allow-net main.ts"
},
"imports": {
"@std/assert": "jsr:@std/assert@1"
}
}
+69
View File
@@ -0,0 +1,69 @@
{
"version": "5",
"specifiers": {
"npm:ts-morph@*": "28.0.0",
"npm:ts-morph@latest": "28.0.0"
},
"npm": {
"@ts-morph/common@0.29.0": {
"integrity": "sha512-35oUmphHbJvQ/+UTwFNme/t2p3FoKiGJ5auTjjpNTop2dyREspirjMy82PLSC1pnDJ8ah1GU98hwpVt64YXQsg==",
"dependencies": [
"minimatch",
"path-browserify",
"tinyglobby"
]
},
"balanced-match@4.0.4": {
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="
},
"brace-expansion@5.0.6": {
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dependencies": [
"balanced-match"
]
},
"code-block-writer@13.0.3": {
"integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="
},
"fdir@6.5.0_picomatch@4.0.4": {
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dependencies": [
"picomatch"
],
"optionalPeers": [
"picomatch"
]
},
"minimatch@10.2.5": {
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dependencies": [
"brace-expansion"
]
},
"path-browserify@1.0.1": {
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
},
"picomatch@4.0.4": {
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="
},
"tinyglobby@0.2.16": {
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"dependencies": [
"fdir",
"picomatch"
]
},
"ts-morph@28.0.0": {
"integrity": "sha512-Wp3tnZ2bzwxyTZMtgWVzXDfm7lB1Drz+y9DmmYH/L702PQhPyVrp3pkou3yIz4qjS14GY9kcpmLiOOMvl8oG1g==",
"dependencies": [
"@ts-morph/common",
"code-block-writer"
]
}
},
"workspace": {
"dependencies": [
"jsr:@std/assert@1"
]
}
}
+187
View File
@@ -0,0 +1,187 @@
// Delete references to utils.ts and replace them with new imports based on the importMap.
// Use this script by running `deno run --allow-read --allow-write --allow-run refactor-import-utils.ts` from the utilsdeno directory.
import { Project } from "npm:ts-morph";
const isDryRun = !Deno.args.includes("--run");
if (isDryRun) {
console.log("=== DRY RUN MODE ===");
console.log(
"To apply changes, run with: deno run --allow-read --allow-write --allow-run refactor-import-utils.ts --run\n"
);
}
// const project = new Project({ tsConfigFilePath: "../src/apps/cli/tsconfig.json" });
const project = new Project({ tsConfigFilePath: "../tsconfig.json" });
const importMap = new Map<string, string>();
const targetFiles = [
"utils.concurrency.ts",
"utils.timer.ts",
"utils.notations.ts",
"utils.database.ts",
"utils.regexp.ts",
"utils.settings.ts",
"utils.patch.ts",
"utils.misc.ts",
];
// 1. Map exports from our newly created subfiles
for (const sourceFile of project.getSourceFiles()) {
const filePath = sourceFile.getFilePath();
const fileName = sourceFile.getBaseName();
if (filePath.includes("src/lib/src/common/") && targetFiles.includes(fileName)) {
const exports = sourceFile.getExportedDeclarations();
for (const [name] of exports) {
const relativePath = filePath.split("src/lib/src/")[1].replace(/\.ts$/, "");
importMap.set(name, `@lib/${relativePath}`);
}
}
}
// 2. Map exports/imports of octagonal-wheels in utils.ts
const utilsFile = project.getSourceFile("src/lib/src/common/utils.ts");
if (utilsFile) {
// Parse imports from octagonal-wheels
for (const imp of utilsFile.getImportDeclarations()) {
const moduleSpec = imp.getModuleSpecifierValue();
if (moduleSpec.startsWith("octagonal-wheels")) {
for (const namedImport of imp.getNamedImports()) {
importMap.set(namedImport.getName(), moduleSpec);
}
}
}
// Parse export declarations from octagonal-wheels
for (const exp of utilsFile.getExportDeclarations()) {
const moduleSpec = exp.getModuleSpecifierValue();
if (moduleSpec && moduleSpec.startsWith("octagonal-wheels")) {
for (const namedExport of exp.getNamedExports()) {
importMap.set(namedExport.getName(), moduleSpec);
}
}
}
}
console.log(`Built importMap with ${importMap.size} mappings.\n`);
let modifiedFilesCount = 0;
// 3. Loop through all source files and replace imports
for (const sourceFile of project.getSourceFiles()) {
let fileModified = false;
const imports = sourceFile.getImportDeclarations();
for (const imp of imports) {
const moduleSpec = imp.getModuleSpecifierValue();
const isUtilsImport =
moduleSpec === "@lib/common/utils" ||
moduleSpec === "@lib/common/utils.ts" ||
moduleSpec.endsWith("/common/utils") ||
moduleSpec.endsWith("/common/utils.ts");
if (isUtilsImport) {
const namedImports = imp.getNamedImports();
const defaultImport = imp.getDefaultImport();
const importsToReplace: Record<string, { name: string; newPath: string; isTypeOnly: boolean }[]> = {};
for (const namedImport of namedImports) {
const name = namedImport.getName();
let newPath = importMap.get(name);
if (newPath) {
// If original ended with .ts and the new path starts with @lib, keep .ts
if (moduleSpec.endsWith(".ts") && newPath.startsWith("@lib/")) {
newPath = newPath + ".ts";
}
if (!importsToReplace[newPath]) {
importsToReplace[newPath] = [];
}
importsToReplace[newPath].push({
name,
newPath,
isTypeOnly: namedImport.isTypeOnly() || imp.isTypeOnly(),
});
}
}
if (Object.keys(importsToReplace).length > 0 || (defaultImport && importMap.has(defaultImport.getText()))) {
fileModified = true;
console.log(`File: ${sourceFile.getFilePath().split("obsidian-livesync/")[1]}`);
console.log(` Old: ${imp.getText()}`);
}
if (!isDryRun) {
// Apply replacements
for (const newPath in importsToReplace) {
const isTypeOnly = importsToReplace[newPath].filter((i) => i.isTypeOnly);
if (isTypeOnly.length > 0) {
sourceFile.insertImportDeclaration(imp.getChildIndex(), {
namedImports: isTypeOnly.map((i) => i.name),
moduleSpecifier: newPath,
isTypeOnly: true,
});
}
const isValueImport = importsToReplace[newPath].filter((i) => !i.isTypeOnly);
if (isValueImport.length > 0) {
sourceFile.insertImportDeclaration(imp.getChildIndex(), {
namedImports: isValueImport.map((i) => i.name),
moduleSpecifier: newPath,
isTypeOnly: false,
});
}
for (const { name } of importsToReplace[newPath]) {
const namedImport = imp.getNamedImports().find((ni) => ni.getName() === name);
if (namedImport) {
namedImport.remove();
}
}
}
} else {
// In dry run, just print what it would do
for (const newPath in importsToReplace) {
const names = importsToReplace[newPath].map((i) => i.name).join(", ");
console.log(` -> Would import { ${names} } from "${newPath}"`);
}
}
if (defaultImport) {
const name = defaultImport.getText();
let newPath = importMap.get(name);
if (newPath) {
if (moduleSpec.endsWith(".ts") && newPath.startsWith("@lib/")) {
newPath = newPath + ".ts";
}
if (!isDryRun) {
sourceFile.insertImportDeclaration(imp.getChildIndex(), {
defaultImport: name,
moduleSpecifier: newPath,
isTypeOnly: imp.isTypeOnly(),
});
imp.removeDefaultImport();
} else {
console.log(` -> Would import default ${name} from "${newPath}"`);
}
}
}
if (!isDryRun) {
if (imp.getNamedImports().length === 0 && !imp.getDefaultImport()) {
imp.remove();
}
}
}
}
if (fileModified) {
modifiedFilesCount++;
}
}
console.log(`\nTotal files to modify: ${modifiedFilesCount}`);
if (!isDryRun) {
project.saveSync();
console.log("All changes successfully saved.");
} else {
console.log("Dry run complete. No changes were written to files.");
}
+155
View File
@@ -0,0 +1,155 @@
// Delete references to types.ts and replace them with new imports based on the importMap. It will also split imports if some are type-only and some are value imports.
// Use this script by running `deno run --allow-read --allow-write --allow-run refactor-imports.ts` from the utilsdeno directory. It will read all source files, find imports from types.ts, and replace them with the new paths based on the importMap. Make sure to review the changes before saving, as it will modify your source files.
import { Project } from "npm:ts-morph";
const isDryRun = !Deno.args.includes("--run");
if (isDryRun) {
console.log("=== DRY RUN MODE ===");
console.log(
"To apply changes, run with: deno run --allow-read --allow-write --allow-run refactor-import-utils.ts --run\n"
);
}
// const project = new Project({ tsConfigFilePath: "../src/apps/cli/tsconfig.json" });
const project = new Project({ tsConfigFilePath: "../tsconfig.json" });
const importMap = new Map<string, string>();
// Build a map of types moved out of Models.
// Under src/lib/src/common/models.
for (const sourceFile of project.getSourceFiles()) {
if (sourceFile.getFilePath().includes("src/lib/src/common/models")) {
const exports = sourceFile.getExportedDeclarations();
for (const [name, declarations] of exports) {
for (const declaration of declarations) {
if (
// declaration.getKindName() === "TypeAliasDeclaration" ||
// declaration.getKindName() === "InterfaceDeclaration" ||
// declaration.getKindName() === "EnumDeclaration" ||
true
) {
// console.log(`Found type export in ${sourceFile.getFilePath()}:`, name);
const relativePath = sourceFile.getFilePath().split("src/lib/src/")[1].replace(/\.ts$/, "");
importMap.set(name, `@lib/${relativePath}`);
}
}
}
}
}
// Extras
importMap.set("LOG_LEVEL_NOTICE", "@lib/common/logger");
importMap.set("LOG_LEVEL_VERBOSE", "@lib/common/logger");
importMap.set("LOG_LEVEL_INFO", "@lib/common/logger");
importMap.set("LOG_LEVEL_DEBUG", "@lib/common/logger");
importMap.set("LOG_LEVEL_URGENT", "@lib/common/logger");
importMap.set("LOG_LEVEL", "@lib/common/logger");
importMap.set("Logger", "@lib/common/logger");
// console.log("Import map:", importMap);
// Loop through all files that import from types.ts.
for (const sourceFile of project.getSourceFiles()) {
const imports = sourceFile.getImportDeclarations();
// if import from types.ts and the file is pointing `/lib/src/common/types.ts` (resolved), then we will check if the imported names exist in the importMap, if yes, we will replace the import path with the new path from importMap.
for (const imp of imports) {
const moduleSpecifier = imp.getModuleSpecifierValue();
if (moduleSpecifier.endsWith("types") || moduleSpecifier.endsWith("types.ts")) {
const filePath = sourceFile.getFilePath();
const lineNumber = imp.getStartLineNumber();
const resolvedModule = imp.getModuleSpecifierSourceFile();
if (!resolvedModule || !resolvedModule.getFilePath().includes("/lib/src/common/types.ts")) {
continue;
}
// Collect imports from types.ts.
const namedImports = imp.getNamedImports();
const defaultImport = imp.getDefaultImport();
console.log(`Found import in ${filePath} at line ${lineNumber}:`, {
namedImports: namedImports.map((ni) => ni.getText()),
defaultImport: defaultImport ? defaultImport.getText() : null,
});
// Group imports by their names and generate new import paths based on the importMap
const importsToReplace: Record<string, { name: string; newPath: string; isTypeOnly: boolean }[]> = {};
for (const namedImport of namedImports) {
const name = namedImport.getName();
const newPath = importMap.get(name);
if (newPath) {
console.log(
`Will replace import of ${name} in ${filePath} at line ${lineNumber} with new path:`,
newPath
);
if (!importsToReplace[newPath]) {
importsToReplace[newPath] = [];
}
importsToReplace[newPath].push({
name,
newPath,
isTypeOnly: namedImport.isTypeOnly() || imp.isTypeOnly(),
});
}
}
// For each import, generate a new path from importMap and replace it.
// Split the import when it needs to become multiple imports.
for (const newPath in importsToReplace) {
// First, handle type-only imports.
const isTypeOnly = importsToReplace[newPath].filter((i) => i.isTypeOnly);
if (isTypeOnly.length > 0) {
sourceFile.insertImportDeclaration(imp.getChildIndex(), {
namedImports: isTypeOnly.map((i) => i.name),
moduleSpecifier: newPath,
isTypeOnly: true,
});
}
// Then, handle non-type-only imports.
const isValueImport = importsToReplace[newPath].filter((i) => !i.isTypeOnly);
if (isValueImport.length > 0) {
sourceFile.insertImportDeclaration(imp.getChildIndex(), {
namedImports: isValueImport.map((i) => i.name),
moduleSpecifier: newPath,
isTypeOnly: false,
});
}
// Remove the replaced named imports from the old import.
for (const { name } of importsToReplace[newPath]) {
const namedImport = imp.getNamedImports().find((ni) => ni.getName() === name);
if (namedImport) {
namedImport.remove();
}
}
}
// If there is also a default import and it exists in importMap, replace it too.
if (defaultImport) {
const name = defaultImport.getText();
const newPath = importMap.get(name);
if (newPath) {
console.log(
`Replacing default import of ${name} in ${filePath} at line ${lineNumber} with new path:`,
newPath
);
// Add the new import statement.
sourceFile.insertImportDeclaration(imp.getChildIndex(), {
defaultImport: name,
moduleSpecifier: newPath,
isTypeOnly: imp.isTypeOnly(),
});
// Remove the default import from the old import.
imp.removeDefaultImport();
}
}
if (imp.getNamedImports().length === 0 && !imp.getDefaultImport()) {
// Delete the entire import statement if nothing remains.
imp.remove();
}
}
}
}
// Save everything at the end.
if (!isDryRun) {
project.saveSync();
}
+58
View File
@@ -0,0 +1,58 @@
import { Project, SyntaxKind } from "npm:ts-morph";
function processFile(filePath: string) {
const project = new Project();
const sourceFile = project.addSourceFileAtPath(filePath);
// 1. Collect all 'any' type nodes in the file
const anyTypeNodes = sourceFile.getDescendantsOfKind(SyntaxKind.AnyKeyword);
const sourceText = sourceFile.getFullText();
const lineBreak = sourceText.includes("\r\n") ? "\r\n" : "\n";
const lines = sourceText.split(/\r?\n/);
const targetLines = new Set<number>();
// 2. Collect the line numbers that contain 'any'
anyTypeNodes.forEach((anyNode: any) => {
const { line } = sourceFile.getLineAndColumnAtPos(anyNode.getStart());
targetLines.add(line - 1);
});
// 3. Add an inline disable only to lines that contain 'any'
for (const lineIndex of targetLines) {
const line = lines[lineIndex];
if (!line) {
continue;
}
if (line.includes("eslint-disable-line @typescript-eslint/no-explicit-any")) {
continue;
}
lines[lineIndex] = `${line} // eslint-disable-line @typescript-eslint/no-explicit-any`;
}
const updatedSourceText = lines.join(lineBreak);
// Output the result
return updatedSourceText;
}
const targetDir = `./_types/`;
async function processDir(dirPath: string) {
for await (const entry of Deno.readDir(dirPath)) {
if (entry.isDirectory) {
await processDir(`${dirPath}/${entry.name}`);
}
if (entry.isFile && entry.name.endsWith(".d.ts")) {
const filePath = `${dirPath}/${entry.name}`;
console.log(`Processing: ${filePath}`);
const updatedContent = processFile(filePath);
// Write the file. To revert, regenerate it with npm run lib:build:types.
await Deno.writeTextFile(filePath, updatedContent);
// console.log(`Updated content for ${filePath}:\n${updatedContent}\n`);
console.log(`Processed: ${filePath}`);
}
}
}
await processDir(targetDir);