@atlaskit/eslint-plugin-platform 0.7.5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @atlaskit/eslint-plugin-platform
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#146603](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/146603)
8
+ [`73a0361be46a2`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/73a0361be46a2) -
9
+ Created new rule `@atlaskit/platform/ensure-valid-bin-values` which validates bin values in
10
+ package.json files are valid point to files, not directories.
11
+
12
+ ## 0.8.0
13
+
14
+ ### Minor Changes
15
+
16
+ - [#143784](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/143784)
17
+ [`dd1180ff23e22`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/dd1180ff23e22) -
18
+ Created new rule `@atlaskit/platform/ensure-valid-workspace-protocol-usage` which validates the
19
+ yarn workspace protocol is only used in private packages.
20
+
3
21
  ## 0.7.5
4
22
 
5
23
  ### Patch Changes
package/dist/cjs/index.js CHANGED
@@ -15,6 +15,8 @@ var _noDuplicateDependencies = _interopRequireDefault(require("./rules/no-duplic
15
15
  var _noInvalidFeatureFlagUsage = _interopRequireDefault(require("./rules/no-invalid-feature-flag-usage"));
16
16
  var _ensureFeatureFlagPrefix = _interopRequireDefault(require("./rules/ensure-feature-flag-prefix"));
17
17
  var _ensureCriticalDependencyResolutions = _interopRequireDefault(require("./rules/ensure-critical-dependency-resolutions"));
18
+ var _ensureValidWorkspaceProtocolUsage = _interopRequireDefault(require("./rules/ensure-valid-workspace-protocol-usage"));
19
+ var _ensureValidBinValues = _interopRequireDefault(require("./rules/ensure-valid-bin-values"));
18
20
  var _noInvalidStorybookDecoratorUsage = _interopRequireDefault(require("./rules/no-invalid-storybook-decorator-usage"));
19
21
  var _ensurePublishValid = _interopRequireDefault(require("./rules/ensure-publish-valid"));
20
22
  var _ensureNativeAndAfExportsSynced = _interopRequireDefault(require("./rules/ensure-native-and-af-exports-synced"));
@@ -35,6 +37,8 @@ var rules = exports.rules = {
35
37
  'ensure-test-runner-nested-count': _ensureTestRunnerNestedCount.default,
36
38
  'ensure-atlassian-team': _ensureAtlassianTeam.default,
37
39
  'ensure-critical-dependency-resolutions': _ensureCriticalDependencyResolutions.default,
40
+ 'ensure-valid-workspace-protocol-usage': _ensureValidWorkspaceProtocolUsage.default,
41
+ 'ensure-valid-bin-values': _ensureValidBinValues.default,
38
42
  'no-duplicate-dependencies': _noDuplicateDependencies.default,
39
43
  'no-invalid-feature-flag-usage': _noInvalidFeatureFlagUsage.default,
40
44
  'no-pre-post-install-scripts': _noPrePostInstalls.default,
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _fs = _interopRequireDefault(require("fs"));
9
+ var _path = require("path");
10
+ var _handleAstObject = require("../util/handle-ast-object");
11
+ // eslint-disable-next-line import/no-extraneous-dependencies
12
+
13
+ var cwd = process.cwd();
14
+ function checkIsAllBinValuesAreValid(node, packageDir) {
15
+ var binObj = (0, _handleAstObject.getObjectPropertyAsObject)(node, 'bin');
16
+ if (!binObj || !Array.isArray(binObj.properties)) {
17
+ return true;
18
+ }
19
+ return binObj.properties.every(function (p) {
20
+ if (p.type === 'Property' && p.value.type === 'Literal') {
21
+ try {
22
+ var binValue = String(p.value.value);
23
+ var pathToBin = (0, _path.resolve)(cwd, packageDir, binValue);
24
+ // Ignore bin values that point to dist as these files don't always exist
25
+ if (binValue.startsWith('./dist/')) {
26
+ return true;
27
+ }
28
+ return _fs.default.statSync(pathToBin).isFile();
29
+ } catch (err) {
30
+ return false;
31
+ }
32
+ }
33
+ // If it's not a property or doesn't have a literal value, consider it invalid
34
+ return false;
35
+ });
36
+ }
37
+ var rule = {
38
+ meta: {
39
+ type: 'problem',
40
+ docs: {
41
+ description: "Ensures bin values in package.json files are valid.",
42
+ recommended: true
43
+ },
44
+ hasSuggestions: false,
45
+ messages: {
46
+ invalidBinValue: "Invalid bin value. Ensure that the value points to a file and not a directory."
47
+ }
48
+ },
49
+ create: function create(context) {
50
+ var fileName = context.getFilename();
51
+ return {
52
+ ObjectExpression: function ObjectExpression(node) {
53
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
54
+ return;
55
+ }
56
+ var isAllBinValuesValid = checkIsAllBinValuesAreValid(node, (0, _path.dirname)(fileName));
57
+ if (isAllBinValuesValid) {
58
+ return;
59
+ }
60
+ return context.report({
61
+ node: node,
62
+ messageId: 'invalidBinValue'
63
+ });
64
+ }
65
+ };
66
+ }
67
+ };
68
+ var _default = exports.default = rule;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _handleAstObject = require("../util/handle-ast-object");
8
+ // eslint-disable-next-line import/no-extraneous-dependencies
9
+
10
+ var workspaceProtocolRegex = /^workspace:[\^~\*]$/;
11
+
12
+ /**
13
+ * Checks if the workspace protocol is used in either dependencies or devDependencies
14
+ */
15
+ function checkIsWorkspaceProtocolUsed(node) {
16
+ var dependencies = (0, _handleAstObject.getObjectPropertyAsObject)(node, 'dependencies');
17
+ var devDependencies = (0, _handleAstObject.getObjectPropertyAsObject)(node, 'devDependencies');
18
+ return [dependencies, devDependencies].some(function (obj) {
19
+ return obj !== null && obj.properties.some(function (p) {
20
+ if (p.type === 'Property' && p.value.type === 'Literal') {
21
+ return typeof p.value.value === 'string' && workspaceProtocolRegex.test(p.value.value);
22
+ }
23
+ });
24
+ });
25
+ }
26
+ var rule = {
27
+ meta: {
28
+ type: 'problem',
29
+ docs: {
30
+ description: "Ensures the workspace protocol is only used in private packages.",
31
+ recommended: true
32
+ },
33
+ hasSuggestions: false,
34
+ messages: {
35
+ invalidWorkspaceProtocolUsage: "The workspace protocol can not be used in public packages."
36
+ }
37
+ },
38
+ create: function create(context) {
39
+ var fileName = context.getFilename();
40
+ return {
41
+ ObjectExpression: function ObjectExpression(node) {
42
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
43
+ return;
44
+ }
45
+
46
+ // Exit early if we are linting a private package
47
+ var isPrivatePackage = (0, _handleAstObject.getObjectPropertyAsLiteral)(node, 'private') === true;
48
+ if (isPrivatePackage) {
49
+ return;
50
+ }
51
+
52
+ // Check if the workspace protocol is used in either dependencies or devDependencies
53
+ var isWorkspaceProtocolused = checkIsWorkspaceProtocolUsed(node);
54
+ if (!isWorkspaceProtocolused) {
55
+ return;
56
+ }
57
+ return context.report({
58
+ node: node,
59
+ messageId: 'invalidWorkspaceProtocolUsage'
60
+ });
61
+ }
62
+ };
63
+ }
64
+ };
65
+ var _default = exports.default = rule;
@@ -9,6 +9,8 @@ import noDuplicateDependencies from './rules/no-duplicate-dependencies';
9
9
  import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
10
10
  import ensureFeatureFlagPrefix from './rules/ensure-feature-flag-prefix';
11
11
  import ensureCriticalDependencyResolutions from './rules/ensure-critical-dependency-resolutions';
12
+ import ensureValidWorkspaceProtocolUsage from './rules/ensure-valid-workspace-protocol-usage';
13
+ import ensureValidBinValues from './rules/ensure-valid-bin-values';
12
14
  import noInvalidStorybookDecoratorUsage from './rules/no-invalid-storybook-decorator-usage';
13
15
  import ensurePublishValid from './rules/ensure-publish-valid';
14
16
  import ensureNativeAndAfExportsSynced from './rules/ensure-native-and-af-exports-synced';
@@ -27,6 +29,8 @@ export const rules = {
27
29
  'ensure-test-runner-nested-count': ensureTestRunnerNestedCount,
28
30
  'ensure-atlassian-team': ensureAtlassianTeam,
29
31
  'ensure-critical-dependency-resolutions': ensureCriticalDependencyResolutions,
32
+ 'ensure-valid-workspace-protocol-usage': ensureValidWorkspaceProtocolUsage,
33
+ 'ensure-valid-bin-values': ensureValidBinValues,
30
34
  'no-duplicate-dependencies': noDuplicateDependencies,
31
35
  'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
32
36
  'no-pre-post-install-scripts': noPreAndPostInstallScripts,
@@ -0,0 +1,60 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import fs from 'fs';
3
+ import { resolve, dirname } from 'path';
4
+ import { getObjectPropertyAsObject } from '../util/handle-ast-object';
5
+ const cwd = process.cwd();
6
+ function checkIsAllBinValuesAreValid(node, packageDir) {
7
+ const binObj = getObjectPropertyAsObject(node, 'bin');
8
+ if (!binObj || !Array.isArray(binObj.properties)) {
9
+ return true;
10
+ }
11
+ return binObj.properties.every(p => {
12
+ if (p.type === 'Property' && p.value.type === 'Literal') {
13
+ try {
14
+ const binValue = String(p.value.value);
15
+ const pathToBin = resolve(cwd, packageDir, binValue);
16
+ // Ignore bin values that point to dist as these files don't always exist
17
+ if (binValue.startsWith('./dist/')) {
18
+ return true;
19
+ }
20
+ return fs.statSync(pathToBin).isFile();
21
+ } catch (err) {
22
+ return false;
23
+ }
24
+ }
25
+ // If it's not a property or doesn't have a literal value, consider it invalid
26
+ return false;
27
+ });
28
+ }
29
+ const rule = {
30
+ meta: {
31
+ type: 'problem',
32
+ docs: {
33
+ description: `Ensures bin values in package.json files are valid.`,
34
+ recommended: true
35
+ },
36
+ hasSuggestions: false,
37
+ messages: {
38
+ invalidBinValue: `Invalid bin value. Ensure that the value points to a file and not a directory.`
39
+ }
40
+ },
41
+ create(context) {
42
+ const fileName = context.getFilename();
43
+ return {
44
+ ObjectExpression: node => {
45
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
46
+ return;
47
+ }
48
+ const isAllBinValuesValid = checkIsAllBinValuesAreValid(node, dirname(fileName));
49
+ if (isAllBinValuesValid) {
50
+ return;
51
+ }
52
+ return context.report({
53
+ node,
54
+ messageId: 'invalidBinValue'
55
+ });
56
+ }
57
+ };
58
+ }
59
+ };
60
+ export default rule;
@@ -0,0 +1,59 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+
3
+ import { getObjectPropertyAsObject, getObjectPropertyAsLiteral } from '../util/handle-ast-object';
4
+ const workspaceProtocolRegex = /^workspace:[\^~\*]$/;
5
+
6
+ /**
7
+ * Checks if the workspace protocol is used in either dependencies or devDependencies
8
+ */
9
+ function checkIsWorkspaceProtocolUsed(node) {
10
+ const dependencies = getObjectPropertyAsObject(node, 'dependencies');
11
+ const devDependencies = getObjectPropertyAsObject(node, 'devDependencies');
12
+ return [dependencies, devDependencies].some(obj => {
13
+ return obj !== null && obj.properties.some(p => {
14
+ if (p.type === 'Property' && p.value.type === 'Literal') {
15
+ return typeof p.value.value === 'string' && workspaceProtocolRegex.test(p.value.value);
16
+ }
17
+ });
18
+ });
19
+ }
20
+ const rule = {
21
+ meta: {
22
+ type: 'problem',
23
+ docs: {
24
+ description: `Ensures the workspace protocol is only used in private packages.`,
25
+ recommended: true
26
+ },
27
+ hasSuggestions: false,
28
+ messages: {
29
+ invalidWorkspaceProtocolUsage: `The workspace protocol can not be used in public packages.`
30
+ }
31
+ },
32
+ create(context) {
33
+ const fileName = context.getFilename();
34
+ return {
35
+ ObjectExpression: node => {
36
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
37
+ return;
38
+ }
39
+
40
+ // Exit early if we are linting a private package
41
+ const isPrivatePackage = getObjectPropertyAsLiteral(node, 'private') === true;
42
+ if (isPrivatePackage) {
43
+ return;
44
+ }
45
+
46
+ // Check if the workspace protocol is used in either dependencies or devDependencies
47
+ const isWorkspaceProtocolused = checkIsWorkspaceProtocolUsed(node);
48
+ if (!isWorkspaceProtocolused) {
49
+ return;
50
+ }
51
+ return context.report({
52
+ node,
53
+ messageId: 'invalidWorkspaceProtocolUsage'
54
+ });
55
+ }
56
+ };
57
+ }
58
+ };
59
+ export default rule;
package/dist/esm/index.js CHANGED
@@ -12,6 +12,8 @@ import noDuplicateDependencies from './rules/no-duplicate-dependencies';
12
12
  import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
13
13
  import ensureFeatureFlagPrefix from './rules/ensure-feature-flag-prefix';
14
14
  import ensureCriticalDependencyResolutions from './rules/ensure-critical-dependency-resolutions';
15
+ import ensureValidWorkspaceProtocolUsage from './rules/ensure-valid-workspace-protocol-usage';
16
+ import ensureValidBinValues from './rules/ensure-valid-bin-values';
15
17
  import noInvalidStorybookDecoratorUsage from './rules/no-invalid-storybook-decorator-usage';
16
18
  import ensurePublishValid from './rules/ensure-publish-valid';
17
19
  import ensureNativeAndAfExportsSynced from './rules/ensure-native-and-af-exports-synced';
@@ -30,6 +32,8 @@ export var rules = {
30
32
  'ensure-test-runner-nested-count': ensureTestRunnerNestedCount,
31
33
  'ensure-atlassian-team': ensureAtlassianTeam,
32
34
  'ensure-critical-dependency-resolutions': ensureCriticalDependencyResolutions,
35
+ 'ensure-valid-workspace-protocol-usage': ensureValidWorkspaceProtocolUsage,
36
+ 'ensure-valid-bin-values': ensureValidBinValues,
33
37
  'no-duplicate-dependencies': noDuplicateDependencies,
34
38
  'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
35
39
  'no-pre-post-install-scripts': noPreAndPostInstallScripts,
@@ -0,0 +1,60 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import fs from 'fs';
3
+ import { resolve, dirname } from 'path';
4
+ import { getObjectPropertyAsObject } from '../util/handle-ast-object';
5
+ var cwd = process.cwd();
6
+ function checkIsAllBinValuesAreValid(node, packageDir) {
7
+ var binObj = getObjectPropertyAsObject(node, 'bin');
8
+ if (!binObj || !Array.isArray(binObj.properties)) {
9
+ return true;
10
+ }
11
+ return binObj.properties.every(function (p) {
12
+ if (p.type === 'Property' && p.value.type === 'Literal') {
13
+ try {
14
+ var binValue = String(p.value.value);
15
+ var pathToBin = resolve(cwd, packageDir, binValue);
16
+ // Ignore bin values that point to dist as these files don't always exist
17
+ if (binValue.startsWith('./dist/')) {
18
+ return true;
19
+ }
20
+ return fs.statSync(pathToBin).isFile();
21
+ } catch (err) {
22
+ return false;
23
+ }
24
+ }
25
+ // If it's not a property or doesn't have a literal value, consider it invalid
26
+ return false;
27
+ });
28
+ }
29
+ var rule = {
30
+ meta: {
31
+ type: 'problem',
32
+ docs: {
33
+ description: "Ensures bin values in package.json files are valid.",
34
+ recommended: true
35
+ },
36
+ hasSuggestions: false,
37
+ messages: {
38
+ invalidBinValue: "Invalid bin value. Ensure that the value points to a file and not a directory."
39
+ }
40
+ },
41
+ create: function create(context) {
42
+ var fileName = context.getFilename();
43
+ return {
44
+ ObjectExpression: function ObjectExpression(node) {
45
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
46
+ return;
47
+ }
48
+ var isAllBinValuesValid = checkIsAllBinValuesAreValid(node, dirname(fileName));
49
+ if (isAllBinValuesValid) {
50
+ return;
51
+ }
52
+ return context.report({
53
+ node: node,
54
+ messageId: 'invalidBinValue'
55
+ });
56
+ }
57
+ };
58
+ }
59
+ };
60
+ export default rule;
@@ -0,0 +1,59 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+
3
+ import { getObjectPropertyAsObject, getObjectPropertyAsLiteral } from '../util/handle-ast-object';
4
+ var workspaceProtocolRegex = /^workspace:[\^~\*]$/;
5
+
6
+ /**
7
+ * Checks if the workspace protocol is used in either dependencies or devDependencies
8
+ */
9
+ function checkIsWorkspaceProtocolUsed(node) {
10
+ var dependencies = getObjectPropertyAsObject(node, 'dependencies');
11
+ var devDependencies = getObjectPropertyAsObject(node, 'devDependencies');
12
+ return [dependencies, devDependencies].some(function (obj) {
13
+ return obj !== null && obj.properties.some(function (p) {
14
+ if (p.type === 'Property' && p.value.type === 'Literal') {
15
+ return typeof p.value.value === 'string' && workspaceProtocolRegex.test(p.value.value);
16
+ }
17
+ });
18
+ });
19
+ }
20
+ var rule = {
21
+ meta: {
22
+ type: 'problem',
23
+ docs: {
24
+ description: "Ensures the workspace protocol is only used in private packages.",
25
+ recommended: true
26
+ },
27
+ hasSuggestions: false,
28
+ messages: {
29
+ invalidWorkspaceProtocolUsage: "The workspace protocol can not be used in public packages."
30
+ }
31
+ },
32
+ create: function create(context) {
33
+ var fileName = context.getFilename();
34
+ return {
35
+ ObjectExpression: function ObjectExpression(node) {
36
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
37
+ return;
38
+ }
39
+
40
+ // Exit early if we are linting a private package
41
+ var isPrivatePackage = getObjectPropertyAsLiteral(node, 'private') === true;
42
+ if (isPrivatePackage) {
43
+ return;
44
+ }
45
+
46
+ // Check if the workspace protocol is used in either dependencies or devDependencies
47
+ var isWorkspaceProtocolused = checkIsWorkspaceProtocolUsed(node);
48
+ if (!isWorkspaceProtocolused) {
49
+ return;
50
+ }
51
+ return context.report({
52
+ node: node,
53
+ messageId: 'invalidWorkspaceProtocolUsage'
54
+ });
55
+ }
56
+ };
57
+ }
58
+ };
59
+ export default rule;
@@ -6,6 +6,8 @@ export declare const rules: {
6
6
  'ensure-test-runner-nested-count': import("eslint").Rule.RuleModule;
7
7
  'ensure-atlassian-team': import("eslint").Rule.RuleModule;
8
8
  'ensure-critical-dependency-resolutions': import("eslint").Rule.RuleModule;
9
+ 'ensure-valid-workspace-protocol-usage': import("eslint").Rule.RuleModule;
10
+ 'ensure-valid-bin-values': import("eslint").Rule.RuleModule;
9
11
  'no-duplicate-dependencies': import("eslint").Rule.RuleModule;
10
12
  'no-invalid-feature-flag-usage': import("eslint").Rule.RuleModule;
11
13
  'no-pre-post-install-scripts': import("eslint").Rule.RuleModule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -6,6 +6,8 @@ export declare const rules: {
6
6
  'ensure-test-runner-nested-count': import("eslint").Rule.RuleModule;
7
7
  'ensure-atlassian-team': import("eslint").Rule.RuleModule;
8
8
  'ensure-critical-dependency-resolutions': import("eslint").Rule.RuleModule;
9
+ 'ensure-valid-workspace-protocol-usage': import("eslint").Rule.RuleModule;
10
+ 'ensure-valid-bin-values': import("eslint").Rule.RuleModule;
9
11
  'no-duplicate-dependencies': import("eslint").Rule.RuleModule;
10
12
  'no-invalid-feature-flag-usage': import("eslint").Rule.RuleModule;
11
13
  'no-pre-post-install-scripts': import("eslint").Rule.RuleModule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@atlaskit/eslint-plugin-platform",
3
3
  "description": "The essential plugin for use with Atlassian frontend platform tools",
4
- "version": "0.7.5",
4
+ "version": "0.9.0",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "atlassian": {
7
7
  "team": "UIP Dev Infra",
package/src/index.tsx CHANGED
@@ -9,6 +9,8 @@ import noDuplicateDependencies from './rules/no-duplicate-dependencies';
9
9
  import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
10
10
  import ensureFeatureFlagPrefix from './rules/ensure-feature-flag-prefix';
11
11
  import ensureCriticalDependencyResolutions from './rules/ensure-critical-dependency-resolutions';
12
+ import ensureValidWorkspaceProtocolUsage from './rules/ensure-valid-workspace-protocol-usage';
13
+ import ensureValidBinValues from './rules/ensure-valid-bin-values';
12
14
  import noInvalidStorybookDecoratorUsage from './rules/no-invalid-storybook-decorator-usage';
13
15
  import ensurePublishValid from './rules/ensure-publish-valid';
14
16
  import ensureNativeAndAfExportsSynced from './rules/ensure-native-and-af-exports-synced';
@@ -28,6 +30,8 @@ export const rules = {
28
30
  'ensure-test-runner-nested-count': ensureTestRunnerNestedCount,
29
31
  'ensure-atlassian-team': ensureAtlassianTeam,
30
32
  'ensure-critical-dependency-resolutions': ensureCriticalDependencyResolutions,
33
+ 'ensure-valid-workspace-protocol-usage': ensureValidWorkspaceProtocolUsage,
34
+ 'ensure-valid-bin-values': ensureValidBinValues,
31
35
  'no-duplicate-dependencies': noDuplicateDependencies,
32
36
  'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
33
37
  'no-pre-post-install-scripts': noPreAndPostInstallScripts,
@@ -0,0 +1,158 @@
1
+ import { tester } from '../../../../__tests__/utils/_tester';
2
+ import rule from '../../index';
3
+
4
+ const cwd = process.cwd();
5
+
6
+ const mockValidBinPaths = [
7
+ `${cwd}/packages/foo/run-ts.bin`,
8
+ `${cwd}/packages/foo/scripts/run-ts.bin`,
9
+ `${cwd}/packages/foo/bar/run-ts.ts`,
10
+ `${cwd}/packages/foo/bar/scripts/run-ts.ts`,
11
+ `${cwd}/packages/baz/run-ts.js`,
12
+ `${cwd}/packages/baz/scripts/run-ts.js`,
13
+ ];
14
+
15
+ jest.mock('fs', () => {
16
+ const actual = jest.requireActual('fs');
17
+ return {
18
+ ...actual,
19
+ statSync: jest.fn((stat: string) => ({
20
+ isFile: jest.fn(() => mockValidBinPaths.includes(stat)),
21
+ })),
22
+ };
23
+ });
24
+
25
+ describe('test ensure-valid-bin-values rule', () => {
26
+ tester.run('ensure-valid-bin-values', rule, {
27
+ valid: [
28
+ // .bin files are valid
29
+ {
30
+ code: `const foo = {
31
+ "bin": {
32
+ "run-ts": "./run-ts.bin",
33
+ }
34
+ }`,
35
+ filename: `${cwd}/packages/foo/package.json`,
36
+ },
37
+ {
38
+ code: `const foo = {
39
+ "bin": {
40
+ "run-ts": "./scripts/run-ts.bin",
41
+ }
42
+ }`,
43
+ filename: `${cwd}/packages/foo/package.json`,
44
+ },
45
+ // .ts files are valid
46
+ {
47
+ code: `const foo = {
48
+ "bin": {
49
+ "run-ts": "./run-ts.ts",
50
+ }
51
+ }`,
52
+ filename: `${cwd}/packages/foo/bar/package.json`,
53
+ },
54
+ {
55
+ code: `const foo = {
56
+ "bin": {
57
+ "run-ts": "./scripts/run-ts.ts",
58
+ }
59
+ }`,
60
+ filename: `${cwd}/packages/foo/bar/package.json`,
61
+ },
62
+ // .js files are valid
63
+ {
64
+ code: `const foo = {
65
+ "bin": {
66
+ "run-ts": "./run-ts.js",
67
+ }
68
+ }`,
69
+ filename: `${cwd}/packages/baz/package.json`,
70
+ },
71
+ {
72
+ code: `const foo = {
73
+ "bin": {
74
+ "run-ts": "./scripts/run-ts.js",
75
+ }
76
+ }`,
77
+ filename: `${cwd}/packages/baz/package.json`,
78
+ },
79
+ // dist paths are valid
80
+ {
81
+ code: `const foo = {
82
+ "bin": {
83
+ "run-ts": "./dist/run-ts.js",
84
+ }
85
+ }`,
86
+ filename: `${cwd}/packages/baz/package.json`,
87
+ },
88
+ ],
89
+ invalid: [
90
+ // Pointing to anything other than a file is invalid
91
+ {
92
+ code: `const foo = {
93
+ "bin": {
94
+ "run-ts": "./bin",
95
+ }
96
+ }`,
97
+ filename: `${cwd}/packages/foo/package.json`,
98
+ errors: [
99
+ {
100
+ messageId: 'invalidBinValue',
101
+ },
102
+ ],
103
+ },
104
+ {
105
+ code: `const foo = {
106
+ "bin": {
107
+ "run-ts": "./scripts/index",
108
+ }
109
+ }`,
110
+ filename: `${cwd}/packages/foo/package.json`,
111
+ errors: [
112
+ {
113
+ messageId: 'invalidBinValue',
114
+ },
115
+ ],
116
+ },
117
+ {
118
+ code: `const foo = {
119
+ "bin": {
120
+ "run-ts": "./scripts/bin",
121
+ }
122
+ }`,
123
+ filename: `${cwd}/packages/foo/package.json`,
124
+ errors: [
125
+ {
126
+ messageId: 'invalidBinValue',
127
+ },
128
+ ],
129
+ },
130
+ {
131
+ code: `const foo = {
132
+ "bin": {
133
+ "run-ts": "./",
134
+ }
135
+ }`,
136
+ filename: `${cwd}/packages/foo/package.json`,
137
+ errors: [
138
+ {
139
+ messageId: 'invalidBinValue',
140
+ },
141
+ ],
142
+ },
143
+ {
144
+ code: `const foo = {
145
+ "bin": {
146
+ "run-ts": "",
147
+ }
148
+ }`,
149
+ filename: `${cwd}/packages/foo/package.json`,
150
+ errors: [
151
+ {
152
+ messageId: 'invalidBinValue',
153
+ },
154
+ ],
155
+ },
156
+ ],
157
+ });
158
+ });
@@ -0,0 +1,70 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import fs from 'fs';
3
+ import { resolve, dirname } from 'path';
4
+ import type { Rule } from 'eslint';
5
+ import type { ObjectExpression } from 'estree';
6
+ import { getObjectPropertyAsObject } from '../util/handle-ast-object';
7
+
8
+ const cwd = process.cwd();
9
+
10
+ function checkIsAllBinValuesAreValid(node: ObjectExpression, packageDir: string) {
11
+ const binObj = getObjectPropertyAsObject(node, 'bin');
12
+
13
+ if (!binObj || !Array.isArray(binObj.properties)) {
14
+ return true;
15
+ }
16
+
17
+ return binObj.properties.every((p) => {
18
+ if (p.type === 'Property' && p.value.type === 'Literal') {
19
+ try {
20
+ const binValue = String(p.value.value);
21
+ const pathToBin = resolve(cwd, packageDir, binValue);
22
+ // Ignore bin values that point to dist as these files don't always exist
23
+ if (binValue.startsWith('./dist/')) {
24
+ return true;
25
+ }
26
+ return fs.statSync(pathToBin).isFile();
27
+ } catch (err) {
28
+ return false;
29
+ }
30
+ }
31
+ // If it's not a property or doesn't have a literal value, consider it invalid
32
+ return false;
33
+ });
34
+ }
35
+
36
+ const rule: Rule.RuleModule = {
37
+ meta: {
38
+ type: 'problem',
39
+ docs: {
40
+ description: `Ensures bin values in package.json files are valid.`,
41
+ recommended: true,
42
+ },
43
+ hasSuggestions: false,
44
+ messages: {
45
+ invalidBinValue: `Invalid bin value. Ensure that the value points to a file and not a directory.`,
46
+ },
47
+ },
48
+ create(context) {
49
+ const fileName = context.getFilename();
50
+ return {
51
+ ObjectExpression: (node: Rule.Node) => {
52
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
53
+ return;
54
+ }
55
+
56
+ const isAllBinValuesValid = checkIsAllBinValuesAreValid(node, dirname(fileName));
57
+ if (isAllBinValuesValid) {
58
+ return;
59
+ }
60
+
61
+ return context.report({
62
+ node,
63
+ messageId: 'invalidBinValue',
64
+ });
65
+ },
66
+ };
67
+ },
68
+ };
69
+
70
+ export default rule;
@@ -0,0 +1,99 @@
1
+ import { tester } from '../../../../__tests__/utils/_tester';
2
+ import rule from '../../index';
3
+
4
+ const cwd = process.cwd();
5
+
6
+ describe('test ensure-valid-workspace-protocol-usage rule', () => {
7
+ tester.run('ensure-valid-workspace-protocol-usage', rule, {
8
+ // Workspace protocol is allowed in private packages as dependencies
9
+ valid: [
10
+ {
11
+ code: `const foo = {
12
+ "private": true,
13
+ "dependencies": {
14
+ "@atlaskit/button": "workspace:^",
15
+ "@atlaskit/primitives": "workspace:*",
16
+ "@atlaskit/tokens": "workspace:*"
17
+ }
18
+ }`,
19
+ filename: `${cwd}/packages/foo/package.json`,
20
+ },
21
+ // Workspace protocol is allowed in private packages as devDependencies
22
+ {
23
+ code: `const foo = {
24
+ "private": true,
25
+ "devDependencies": {
26
+ "@atlaskit/button": "workspace:^",
27
+ "@atlaskit/primitives": "workspace:^"
28
+ }
29
+ }`,
30
+ filename: `${cwd}/packages/foo/package.json`,
31
+ },
32
+ // Workspace protocol is allowed in private packages dependencies and devDependencies
33
+ {
34
+ code: `const foo = {
35
+ "private": true,
36
+ "dependencies": {
37
+ "@atlaskit/button": "workspace:^",
38
+ "@atlaskit/primitives": "workspace:^"
39
+ },
40
+ "devDependencies": {
41
+ "@atlaskit/button": "workspace:^",
42
+ "@atlaskit/primitives": "workspace:^"
43
+ }
44
+ }`,
45
+ filename: `${cwd}/packages/foo/package.json`,
46
+ },
47
+ ],
48
+ invalid: [
49
+ // Workspace protocol is not allowed in public packages as dependencies
50
+ {
51
+ code: `const foo = {
52
+ "dependencies": {
53
+ "@atlaskit/button": "workspace:^",
54
+ "@atlaskit/primitives": "workspace:*",
55
+ "@atlaskit/tokens": "workspace:*"
56
+ }
57
+ }`,
58
+ filename: `${cwd}/packages/foo/package.json`,
59
+ errors: [
60
+ {
61
+ messageId: 'invalidWorkspaceProtocolUsage',
62
+ },
63
+ ],
64
+ },
65
+ // Workspace protocol is not allowed in public packages as devDependencies
66
+ {
67
+ code: `const foo = {
68
+ "devDependencies": {
69
+ "@atlaskit/button": "workspace:^",
70
+ "@atlaskit/primitives": "^1.0.0"
71
+ }
72
+ }`,
73
+ filename: `${cwd}/packages/foo/package.json`,
74
+ errors: [
75
+ {
76
+ messageId: 'invalidWorkspaceProtocolUsage',
77
+ },
78
+ ],
79
+ },
80
+ // Workspace protocol is not allowed in public packages as dependencies and devDependencies
81
+ {
82
+ code: `const foo = {
83
+ "dependencies": {
84
+ "@atlaskit/button": "workspace:^"
85
+ },
86
+ "devDependencies": {
87
+ "@atlaskit/primitives": "workspace:^"
88
+ }
89
+ }`,
90
+ filename: `${cwd}/packages/foo/package.json`,
91
+ errors: [
92
+ {
93
+ messageId: 'invalidWorkspaceProtocolUsage',
94
+ },
95
+ ],
96
+ },
97
+ ],
98
+ });
99
+ });
@@ -0,0 +1,67 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import type { Rule } from 'eslint';
3
+ import type { ObjectExpression } from 'estree';
4
+ import { getObjectPropertyAsObject, getObjectPropertyAsLiteral } from '../util/handle-ast-object';
5
+
6
+ const workspaceProtocolRegex = /^workspace:[\^~\*]$/;
7
+
8
+ /**
9
+ * Checks if the workspace protocol is used in either dependencies or devDependencies
10
+ */
11
+ function checkIsWorkspaceProtocolUsed(node: ObjectExpression) {
12
+ const dependencies = getObjectPropertyAsObject(node, 'dependencies');
13
+ const devDependencies = getObjectPropertyAsObject(node, 'devDependencies');
14
+ return [dependencies, devDependencies].some((obj) => {
15
+ return (
16
+ obj !== null &&
17
+ obj.properties.some((p) => {
18
+ if (p.type === 'Property' && p.value.type === 'Literal') {
19
+ return typeof p.value.value === 'string' && workspaceProtocolRegex.test(p.value.value);
20
+ }
21
+ })
22
+ );
23
+ });
24
+ }
25
+
26
+ const rule: Rule.RuleModule = {
27
+ meta: {
28
+ type: 'problem',
29
+ docs: {
30
+ description: `Ensures the workspace protocol is only used in private packages.`,
31
+ recommended: true,
32
+ },
33
+ hasSuggestions: false,
34
+ messages: {
35
+ invalidWorkspaceProtocolUsage: `The workspace protocol can not be used in public packages.`,
36
+ },
37
+ },
38
+ create(context) {
39
+ const fileName = context.getFilename();
40
+ return {
41
+ ObjectExpression: (node: Rule.Node) => {
42
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
43
+ return;
44
+ }
45
+
46
+ // Exit early if we are linting a private package
47
+ const isPrivatePackage = getObjectPropertyAsLiteral(node, 'private') === true;
48
+ if (isPrivatePackage) {
49
+ return;
50
+ }
51
+
52
+ // Check if the workspace protocol is used in either dependencies or devDependencies
53
+ const isWorkspaceProtocolused = checkIsWorkspaceProtocolUsed(node);
54
+ if (!isWorkspaceProtocolused) {
55
+ return;
56
+ }
57
+
58
+ return context.report({
59
+ node,
60
+ messageId: 'invalidWorkspaceProtocolUsage',
61
+ });
62
+ },
63
+ };
64
+ },
65
+ };
66
+
67
+ export default rule;