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

fix: handle invalid directory errors when creating atomic component #1327

Closed
wants to merge 14 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const main = () => {
} else {
handleErrors(
new InvalidProjectDirectory(
'Current working directory is either not empty or not an npm project (no package.json found). Please try again in an empty directory.'
'Current working directory is either not empty or not an npm project (no package.json found). Please try again in a valid project (see [`atomic:cmp` documentation](https://docs.coveo.com/en/cli/#coveo-atomiccmp-name)).'
)
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ChildProcess} from 'node:child_process';
import {join} from 'node:path';
import {mkdirSync} from 'node:fs';
import {mkdirSync, writeFileSync, rmSync} from 'node:fs';
import {lookup} from 'node:dns/promises';
import {npmSync, npmAsync} from '@coveo/do-npm';
import {getStdoutStderrBuffersFromProcess} from '@coveo/process-helpers';
Expand Down Expand Up @@ -135,4 +135,74 @@ describe(PACKAGE_NAME, () => {
expect(interceptedRequests.some(isSearchRequestOrResponse)).toBe(true);
});
});

describe('ensureDirectoryValidity', () => {
const assertAggregateErrorsNotFired = (
tag: string,
expectedErrorMessages: string
) => {
const {stderr} = npmSync(
['init', PACKAGE_NAME.replace('/create-', '/'), '--', tag],
{
env: {
...process.env,
npm_config_registry: verdaccioUrl,
npm_config_cache: dirSync({unsafeCleanup: true}).name,
},
cwd: testDirectory,
}
);

expect(stderr.toString()).not.toEqual(
expect.stringContaining(expectedErrorMessages)
);
};

beforeEach(() => {
testDirectory = join(tempDirectory.name, 'dir-validity');
mkdirSync(testDirectory, {recursive: true});
});

afterEach(() => {
rmSync(testDirectory, {recursive: true});
});

it('should not error when in empty directory', () => {
assertAggregateErrorsNotFired(
'component-name',
'Invalid project directory'
);
});

it('should not error when in non-empty directory with package.json', () => {
writeFileSync(join(testDirectory, 'package.json'), '{}');
assertAggregateErrorsNotFired(
'component-name',
'Invalid project directory'
);
});

it('should error when in non-empty directory without package.json', () => {
writeFileSync(
join(testDirectory, '__init__.py'),
"# Wait, this isn't a Headless project :O"
);

const {stderr} = npmSync(
['init', PACKAGE_NAME.replace('/create-', '/'), '--', 'component-name'],
{
env: {
...process.env,
npm_config_registry: verdaccioUrl,
npm_config_cache: dirSync({unsafeCleanup: true}).name,
},
cwd: testDirectory,
}
);

expect(stderr.toString()).toEqual(
expect.stringContaining('Invalid project directory')
);
});
});
});
32 changes: 32 additions & 0 deletions packages/ui/atomic/create-atomic-component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
unlinkSync,
writeFileSync,
readFileSync,
readdirSync,
} from 'node:fs';
import {cwd} from 'node:process';
import {fileURLToPath} from 'node:url';
Expand All @@ -27,6 +28,20 @@ class SerializableAggregateError extends AggregateError {
};
}
}
class InvalidProjectDirectory extends Error {
name = 'Invalid project directory';
constructor(message) {
super(message);
}

toJSON() {
return {
name: this.name,
message: this.message,
stack: this.stack,
};
}
}

const handleErrors = (error) => {
if (process.channel) {
Expand Down Expand Up @@ -63,6 +78,7 @@ const camelize = (str) =>
str
.replace(/-(.)/g, (_, group) => group.toUpperCase())
.replace(/^./, (match) => match.toUpperCase());

const transform = (transformers) => {
for (const {srcPath, destPath, transform} of transformers) {
if (!srcPath) {
Expand Down Expand Up @@ -120,11 +136,27 @@ const ensureComponentValidity = (tag) => {
}
};

const ensureDirectoryValidity = () => {
const cwdFiles = readdirSync(cwd(), {withFileTypes: true});
const hasPackageInCwd = cwdFiles.some(
(dirent) => dirent.name === 'package.json'
);
if (cwdFiles.length > 0 && !hasPackageInCwd) {
handleErrors(
new InvalidProjectDirectory(
'Current working directory is either not empty or not an npm project (no package.json found). Please try again in a valid project (see [`atomic:cmp` documentation](https://docs.coveo.com/en/cli/#coveo-atomiccmp-name)).'
)
);
}
};

/***********************************/
const __dirname = dirname(fileURLToPath(import.meta.url));
const templateRelativeDir = 'template';
const templateDirPath = resolve(__dirname, templateRelativeDir);

ensureDirectoryValidity();

cpSync(templateDirPath, cwd(), {
recursive: true,
});
Expand Down
107 changes: 83 additions & 24 deletions packages/ui/atomic/create-atomic-component/tests/test.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ChildProcess} from 'node:child_process';
import {join} from 'node:path';
import {mkdirSync} from 'node:fs';
import {mkdirSync, writeFileSync, rmSync} from 'node:fs';
import {npmSync} from '@coveo/do-npm';
import {startVerdaccio} from '@coveo/verdaccio-starter';
import {hashElement} from 'folder-hash';
Expand All @@ -18,6 +18,29 @@ describe(PACKAGE_NAME, () => {
let npmCache: string;
let verdaccioUrl: string;

const assertAllAggregateErrorsFired = (
tag: string,
...expectedErrorMessages: string[]
) => {
const {stderr} = npmSync(
['init', PACKAGE_NAME.replace('/create-', '/'), '--', tag],
{
env: {
...process.env,
npm_config_registry: verdaccioUrl,
npm_config_cache: npmCache,
},
cwd: testDirectory,
}
);

expectedErrorMessages.forEach((expectedMessage) => {
expect(stderr.toString()).toEqual(
expect.stringContaining(expectedMessage)
);
});
};

beforeAll(async () => {
({verdaccioUrl, verdaccioProcess} = await startVerdaccio([
PACKAGE_NAME,
Expand Down Expand Up @@ -52,29 +75,6 @@ describe(PACKAGE_NAME, () => {
const leadingDashTag = '-dash';
const messedUpTag = '-My#--Component@-';

const assertAllAggregateErrorsFired = (
tag: string,
...expectedErrorMessages: string[]
) => {
const {stderr} = npmSync(
['init', PACKAGE_NAME.replace('/create-', '/'), '--', tag],
{
env: {
...process.env,
npm_config_registry: verdaccioUrl,
npm_config_cache: npmCache,
},
cwd: testDirectory,
}
);

expectedErrorMessages.forEach((expectedMessage) => {
expect(stderr.toString()).toEqual(
expect.stringContaining(expectedMessage)
);
});
};

it.each([
leadingSpaceTag,
trailingSpaceTag,
Expand Down Expand Up @@ -148,6 +148,65 @@ describe(PACKAGE_NAME, () => {
});
});

describe('ensureDirectoryValidity', () => {
const assertAggregateErrorsNotFired = (
tag: string,
expectedErrorMessages: string
) => {
const {stderr} = npmSync(
['init', PACKAGE_NAME.replace('/create-', '/'), '--', tag],
{
env: {
...process.env,
npm_config_registry: verdaccioUrl,
npm_config_cache: npmCache,
},
cwd: testDirectory,
}
);

expect(stderr.toString()).not.toEqual(
expect.stringContaining(expectedErrorMessages)
);
};

beforeEach(() => {
testDirectory = join(tempDirectory.name, 'dir-validity');
mkdirSync(testDirectory, {recursive: true});
});

afterEach(() => {
rmSync(testDirectory, {recursive: true});
});

it('should not error when in empty directory', () => {
assertAggregateErrorsNotFired(
'component-name',
'Invalid project directory'
);
});

it('should not error when in non-empty directory with package.json', () => {
writeFileSync(join(testDirectory, 'package.json'), '{}');
assertAggregateErrorsNotFired(
'component-name',
'Invalid project directory'
);
});

it('should error when in non-empty directory without package.json', () => {
writeFileSync(
join(testDirectory, '__init__.py'),
"# Wait, this isn't a Headless project :O"
);

assertAllAggregateErrorsFired(
'component-name',
'Invalid project directory'
);
});
});

describe.each([
{
describeName: 'when called without any args',
Expand Down
32 changes: 32 additions & 0 deletions packages/ui/atomic/create-atomic-result-component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
unlinkSync,
writeFileSync,
readFileSync,
readdirSync,
} from 'node:fs';
import {cwd} from 'node:process';
import {fileURLToPath} from 'node:url';
Expand All @@ -28,6 +29,21 @@ class SerializableAggregateError extends AggregateError {
}
}

class InvalidProjectDirectory extends Error {
name = 'Invalid project directory';
constructor(message) {
super(message);
}

toJSON() {
return {
name: this.name,
message: this.message,
stack: this.stack,
};
}
}

const handleErrors = (error) => {
if (process.channel) {
process.send(error);
Expand Down Expand Up @@ -120,11 +136,27 @@ const ensureComponentValidity = (tag) => {
}
};

const ensureDirectoryValidity = () => {
dmbrooke marked this conversation as resolved.
Show resolved Hide resolved
const cwdFiles = readdirSync(cwd(), {withFileTypes: true});
const hasPackageInCwd = cwdFiles.some(
(dirent) => dirent.name === 'package.json'
);
if (cwdFiles.length > 0 && !hasPackageInCwd) {
handleErrors(
new InvalidProjectDirectory(
'Current working directory is either not empty or not an npm project (no package.json found). Please try again in a valid project (see [`atomic:cmp` documentation](https://docs.coveo.com/en/cli/#coveo-atomiccmp-name)).'
)
);
}
};

/***********************************/
const __dirname = dirname(fileURLToPath(import.meta.url));
const templateRelativeDir = 'template';
const templateDirPath = resolve(__dirname, templateRelativeDir);

ensureDirectoryValidity();

cpSync(templateDirPath, cwd(), {
recursive: true,
});
Expand Down
Loading
Loading