Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Git Projects): Insomnia files can be anywhere inside the repository #8432

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion packages/insomnia/src/common/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { type GrpcRequest, isGrpcRequest } from '../models/grpc-request';
import { type BaseModel, getModel, userSession } from '../models/index';
import * as models from '../models/index';
import { isMockRoute, type MockRoute } from '../models/mock-route';
import { isGitProject } from '../models/project';
import { isRequest, type Request } from '../models/request';
import { isRequestGroup } from '../models/request-group';
import { isUnitTest, type UnitTest } from '../models/unit-test';
Expand Down Expand Up @@ -373,6 +374,10 @@ const importResourcesToNewWorkspace = async (
workspaceToImport?: Workspace
) => {
invariant(resourceCacheItem, 'No resources to import');

const project = await models.project.getById(projectId);
invariant(project, 'Project not found');

const resources = resourceCacheItem.resources;
const ResourceIdMap = new Map();
// in order to support import from api spec yaml
Expand All @@ -382,7 +387,15 @@ const importResourcesToNewWorkspace = async (
scope: 'design',
parentId: projectId,
});
models.apiSpec.updateOrCreateForParentId(newWorkspace._id, {

if (isGitProject(project)) {
const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(newWorkspace._id);
await models.workspaceMeta.update(workspaceMeta, {
gitRepoPath: `${newWorkspace.name}-${newWorkspace._id}.yaml`,
});
}

await models.apiSpec.updateOrCreateForParentId(newWorkspace._id, {
contents: resourceCacheItem.content as string | undefined,
contentType: 'yaml',
fileName: workspaceToImport?.name,
Expand All @@ -397,6 +410,14 @@ const importResourcesToNewWorkspace = async (
scope: workspaceToImport?.scope || 'collection',
parentId: projectId,
});

if (isGitProject(project)) {
const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(newWorkspace._id);
await models.workspaceMeta.update(workspaceMeta, {
gitRepoPath: `${newWorkspace.name}-${newWorkspace._id}.yaml`,
});
}

const apiSpec = resources.find(r => r.type === 'ApiSpec' && r.parentId === workspaceToImport?._id) as ApiSpec;
const hasApiSpec = newWorkspace.scope === 'design' && isApiSpec(apiSpec);
// if workspace is not in the resources, there will be no apiSpec, if resource type is set to api spec this could cause a bug
Expand Down
124 changes: 116 additions & 8 deletions packages/insomnia/src/main/git-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { shell } from 'electron';
import { app, net } from 'electron/main';
import { fromUrl } from 'hosted-git-info';
import { Errors } from 'isomorphic-git';
import { Errors, type PromiseFsClient } from 'isomorphic-git';
import path from 'path';
import { v4 } from 'uuid';
import YAML, { parse } from 'yaml';
Expand All @@ -15,7 +15,7 @@
import type { GitRepository } from '../models/git-repository';
import { type WorkspaceScope, WorkspaceScopeKeys } from '../models/workspace';
import { fsClient } from '../sync/git/fs-client';
import GitVCS, { GIT_CLONE_DIR, GIT_INSOMNIA_DIR, GIT_INSOMNIA_DIR_NAME, GIT_INTERNAL_DIR } from '../sync/git/git-vcs';
import GitVCS, { GIT_CLONE_DIR, GIT_INSOMNIA_DIR, GIT_INSOMNIA_DIR_NAME, GIT_INTERNAL_DIR, MergeConflictError } from '../sync/git/git-vcs';
import { MemClient } from '../sync/git/mem-client';
import { NeDBClient } from '../sync/git/ne-db-client';
import { GitProjectNeDBClient } from '../sync/git/project-ne-db-client';
Expand Down Expand Up @@ -165,7 +165,7 @@
const fsClient = await getGitFSClient({ gitRepositoryId: gitRepository._id, projectId, workspaceId });

// Init VCS
const { credentials, uri, author } = gitRepository;
const { credentials, uri } = gitRepository;
if (gitRepository.needsFullClone) {
await GitVCS.initFromClone({
repoId: gitRepository._id,
Expand All @@ -187,11 +187,12 @@
fs: fsClient,
gitDirectory: GIT_INTERNAL_DIR,
gitCredentials: credentials,
legacyDiff: Boolean(workspaceId),
});
}

// Configure basic info
await GitVCS.setAuthor(author.name, author.email);
await GitVCS.setAuthor();
await GitVCS.addRemote(uri);

return {
Expand Down Expand Up @@ -352,6 +353,28 @@
}
};

const recursivelyFindInsomniaFiles = async (fsClient: PromiseFsClient, dir: string, files: string[] = []) => {
const dirFiles = await fsClient.promises.readdir(dir);
for (const file of dirFiles) {
const fullPath = path.join(dir, file);

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal Warning

Detected possible user input going into a path.join or path.resolve function. This could possibly lead to a path traversal vulnerability, where the attacker can access arbitrary files stored in the file system. Instead, be sure to sanitize or validate user input first.

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal Warning

Detected possible user input going into a path.join or path.resolve function. This could possibly lead to a path traversal vulnerability, where the attacker can access arbitrary files stored in the file system. Instead, be sure to sanitize or validate user input first.
const stats = await fsClient.promises.stat(fullPath);

if (stats.isDirectory()) {
await recursivelyFindInsomniaFiles(fsClient, fullPath, files);
} else {
if (fullPath.endsWith('.yaml')) {
const fileContents = await fsClient.promises.readFile(fullPath, 'utf8');
const isInsomniaFile = fileContents.split('\n')[0].trim().includes('insomnia.rest');
if (isInsomniaFile) {
files.push(fullPath);
}
}
}
}

return files;
};

// Actions
export const initGitRepoCloneAction = async ({
uri,
Expand Down Expand Up @@ -429,8 +452,8 @@
};
}

const rootDirFiles: string[] = await inMemoryFsClient.promises.readdir(GIT_CLONE_DIR);
const insomniaFiles = rootDirFiles.filter(fileOrFolder => fileOrFolder.startsWith('insomnia.'));
const insomniaFiles = await recursivelyFindInsomniaFiles(inMemoryFsClient, GIT_CLONE_DIR);
// Get all files that start with 'insomnia.' recursively in the root directory

const files = await Promise.all(
insomniaFiles.map(async file => {
Expand Down Expand Up @@ -602,7 +625,7 @@
});
}

await GitVCS.setAuthor(gitRepository.author.name, gitRepository.author.email);
await GitVCS.setAuthor();
await GitVCS.addRemote(uri);

await database.flushChanges(bufferId);
Expand Down Expand Up @@ -827,10 +850,11 @@
fs: routableFS,
gitDirectory: GIT_INTERNAL_DIR,
gitCredentials: gitRepository.credentials,
legacyDiff: true,
});
}

await GitVCS.setAuthor(gitRepository.author.name, gitRepository.author.email);
await GitVCS.setAuthor();
await GitVCS.addRemote(uri);
}

Expand Down Expand Up @@ -955,6 +979,8 @@
hasUnpushedChanges,
});

await GitVCS.setAuthor();

return null;
} catch (e) {
return {
Expand Down Expand Up @@ -1260,6 +1286,10 @@
await database.flushChanges(bufferId, true);
return {};
} catch (err) {
if (err instanceof MergeConflictError) {
return err.data;
}

if (err instanceof Errors.HttpError) {
err = new Error(`${err.message}, ${err.data.response}`);
}
Expand Down Expand Up @@ -1407,6 +1437,10 @@

return {};
} catch (err: unknown) {
if (err instanceof MergeConflictError) {
return err.data;
}

if (err instanceof Errors.HttpError) {
err = new Error(`${err.message}, ${err.data.response}`);
}
Expand Down Expand Up @@ -1675,6 +1709,78 @@
}
};

interface GitRepoFile {
id: string;
name: string;
type: 'file';
}

interface GitRepoDirectory {
id: string;
name: string;
type: 'directory';
children: (GitRepoDirectory | GitRepoFile)[];
};

type FileTree = {
id: string;
name: string;
type: 'root';
children: (GitRepoDirectory | GitRepoFile)[];
} | GitRepoDirectory | GitRepoFile;

const getRepositoryDirectoryTree = async ({ projectId }: { projectId: string }): Promise<{
repositoryTree: FileTree;
folderList: Record<string, string[]>;
}> => {
const gitRepository = await getGitRepository({ projectId });
const fs = await getGitFSClient({ projectId, gitRepositoryId: gitRepository._id });

const rootContents = await fs.promises.readdir(GIT_CLONE_DIR);

const folderList: Record<string, string[]> = {
'': rootContents,
};

const recursivelyGetDirectoryTree = async (directoryContents: string[], parentPath: string) => {
const tree: (GitRepoDirectory | GitRepoFile)[] = await Promise.all(
directoryContents.map(async (file: string) => {
const fileOrDirPath = path.join(parentPath, file);

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal Warning

Detected possible user input going into a path.join or path.resolve function. This could possibly lead to a path traversal vulnerability, where the attacker can access arbitrary files stored in the file system. Instead, be sure to sanitize or validate user input first.

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal Warning

Detected possible user input going into a path.join or path.resolve function. This could possibly lead to a path traversal vulnerability, where the attacker can access arbitrary files stored in the file system. Instead, be sure to sanitize or validate user input first.
const stats = await fs.promises.stat(fileOrDirPath);
if (await stats.isDirectory()) {
const subDirectoryContents = await fs.promises.readdir(fileOrDirPath);
folderList[fileOrDirPath] = subDirectoryContents;
return {
id: fileOrDirPath,
name: file,
type: 'directory',
children: await recursivelyGetDirectoryTree(subDirectoryContents, fileOrDirPath),
};
}
return {
id: fileOrDirPath,
name: file,
type: 'file',
};
}),
);

return tree;
};

const tree = await recursivelyGetDirectoryTree(rootContents, GIT_CLONE_DIR);

return {
repositoryTree: {
id: '',
name: gitRepository.uri.split('/').pop()?.replace('.git', '').toUpperCase() || 'Repository',
type: 'root',
children: tree,
} satisfies FileTree,
folderList,
};
};

export const GITHUB_GRAPHQL_API_URL = getGitHubGraphQLApiURL();

/**
Expand Down Expand Up @@ -2079,6 +2185,7 @@
stageChanges: typeof stageChangesAction;
unstageChanges: typeof unstageChangesAction;
diffFileLoader: typeof diffFileLoader;
getRepositoryDirectoryTree: typeof getRepositoryDirectoryTree;

initSignInToGitHub: typeof initSignInToGitHub;
completeSignInToGitHub: typeof completeSignInToGitHub;
Expand Down Expand Up @@ -2116,6 +2223,7 @@
ipcMainHandle('git.stageChanges', (_, options: Parameters<typeof stageChangesAction>[0]) => stageChangesAction(options));
ipcMainHandle('git.unstageChanges', (_, options: Parameters<typeof unstageChangesAction>[0]) => unstageChangesAction(options));
ipcMainHandle('git.diffFileLoader', (_, options: Parameters<typeof diffFileLoader>[0]) => diffFileLoader(options));
ipcMainHandle('git.getRepositoryDirectoryTree', (_, options: Parameters<typeof getRepositoryDirectoryTree>[0]) => getRepositoryDirectoryTree(options));

ipcMainHandle('git.initSignInToGitHub', () => initSignInToGitHub());
ipcMainHandle('git.completeSignInToGitHub', (_, options: Parameters<typeof completeSignInToGitHub>[0]) => completeSignInToGitHub(options));
Expand Down
1 change: 1 addition & 0 deletions packages/insomnia/src/main/ipc/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export type HandleChannels =
| 'git.stageChanges'
| 'git.unstageChanges'
| 'git.diffFileLoader'
| 'git.getRepositoryDirectoryTree'
| 'git.initSignInToGitHub'
| 'git.completeSignInToGitHub'
| 'git.signOutOfGitHub'
Expand Down
2 changes: 0 additions & 2 deletions packages/insomnia/src/main/window-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,12 +272,10 @@ export function createWindow({ firstLaunch }: { firstLaunch?: boolean } = {}): E
});

mainBrowserWindow.on('focus', () => {
console.log('[main] window focus');
mainBrowserWindow.webContents.send('mainWindowFocusChange', true);
});

mainBrowserWindow.on('blur', () => {
console.log('[main] window blur');
mainBrowserWindow.webContents.send('mainWindowFocusChange', false);
});

Expand Down
2 changes: 2 additions & 0 deletions packages/insomnia/src/models/workspace-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface BaseWorkspaceMeta {
pushSnapshotOnInitialize: boolean;
hasUncommittedChanges: boolean;
hasUnpushedChanges: boolean;
gitRepoPath: string | null;
}

export type WorkspaceMeta = BaseWorkspaceMeta & BaseModel;
Expand All @@ -34,6 +35,7 @@ export function init(): BaseWorkspaceMeta {
activeRequestId: null,
activeUnitTestSuiteId: null,
gitRepositoryId: null,
gitRepoPath: null,
parentId: null,
pushSnapshotOnInitialize: false,
hasUncommittedChanges: false,
Expand Down
1 change: 1 addition & 0 deletions packages/insomnia/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const git: GitServiceAPI = {
stageChanges: options => ipcRenderer.invoke('git.stageChanges', options),
unstageChanges: options => ipcRenderer.invoke('git.unstageChanges', options),
diffFileLoader: options => ipcRenderer.invoke('git.diffFileLoader', options),
getRepositoryDirectoryTree: options => ipcRenderer.invoke('git.getRepositoryDirectoryTree', options),

initSignInToGitHub: () => ipcRenderer.invoke('git.initSignInToGitHub'),
completeSignInToGitHub: options => ipcRenderer.invoke('git.completeSignInToGitHub', options),
Expand Down
6 changes: 6 additions & 0 deletions packages/insomnia/src/sync/git/__tests__/git-vcs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
repoId: '',
directory: GIT_CLONE_DIR,
fs: fsClient,
legacyDiff: true,
});
await GitVCS.setAuthor('Karen Brown', '[email protected]');

Expand Down Expand Up @@ -109,6 +110,7 @@
repoId: '',
directory: GIT_CLONE_DIR,
fs: fsClient,
legacyDiff: true,
});
await GitVCS.setAuthor('Karen Brown', '[email protected]');
expect(await GitVCS.log()).toEqual([]);
Expand All @@ -126,6 +128,7 @@
repoId: '',
directory: GIT_CLONE_DIR,
fs: fsClient,
legacyDiff: true,
});

await GitVCS.setAuthor('Karen Brown', '[email protected]');
Expand Down Expand Up @@ -170,7 +173,7 @@
},
]);

expect(await GitVCS.log()).toEqual([

Check failure on line 176 in packages/insomnia/src/sync/git/__tests__/git-vcs.test.ts

View workflow job for this annotation

GitHub Actions / Test

src/sync/git/__tests__/git-vcs.test.ts > Git-VCS > common operations > commit file

AssertionError: expected [ { …(3) } ] to deeply equal [ { commit: { …(5) }, …(2) } ] - Expected + Received Array [ Object { "commit": Object { "author": Object { - "email": "[email protected]", - "name": "Karen Brown", + "email": "", + "name": "", "timestamp": 1000000000, "timezoneOffset": 0, }, "committer": Object { - "email": "[email protected]", - "name": "Karen Brown", + "email": "", + "name": "", "timestamp": 1000000000, "timezoneOffset": 0, }, "message": "First commit! ", "parent": Array [], "tree": "14819d8019f05edb70a29850deb09a4314ad0afc", }, - "oid": "76f804a23eef9f52017bf93f4bc0bfde45ec8a93", + "oid": "673fd1bed8122b4cd9c30c0a06ce086bcf1dd9fc", "payload": "tree 14819d8019f05edb70a29850deb09a4314ad0afc - author Karen Brown <[email protected]> 1000000000 +0000 - committer Karen Brown <[email protected]> 1000000000 +0000 + author <> 1000000000 +0000 + committer <> 1000000000 +0000 First commit! ", }, ] ❯ src/sync/git/__tests__/git-vcs.test.ts:176:34
{
commit: {
author: {
Expand Down Expand Up @@ -212,6 +215,7 @@
repoId: '',
directory: GIT_CLONE_DIR,
fs: fsClient,
legacyDiff: true,
});
await GitVCS.setAuthor('Karen Brown', '[email protected]');
const status = await GitVCS.status();
Expand Down Expand Up @@ -260,6 +264,7 @@
repoId: '',
directory: GIT_CLONE_DIR,
fs: fsClient,
legacyDiff: true,
});
// Commit
await GitVCS.setAuthor('Karen Brown', '[email protected]');
Expand Down Expand Up @@ -318,6 +323,7 @@
repoId: '',
directory: GIT_CLONE_DIR,
fs: fsClient,
legacyDiff: true,
});
// Write to all files
await Promise.all(files.map(f => fsClient.promises.writeFile(f, originalContent)));
Expand Down
Loading
Loading