@platformos/platformos-check-node 0.0.2
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/README.md +9 -0
- package/configs/all.yml +214 -0
- package/configs/nothing.yml +3 -0
- package/configs/recommended.yml +192 -0
- package/dist/NodeFileSystem.d.ts +2 -0
- package/dist/NodeFileSystem.js +37 -0
- package/dist/autofix.d.ts +6 -0
- package/dist/autofix.js +18 -0
- package/dist/backfill-docs/argument-collector.d.ts +6 -0
- package/dist/backfill-docs/argument-collector.js +167 -0
- package/dist/backfill-docs/doc-generator.d.ts +18 -0
- package/dist/backfill-docs/doc-generator.js +31 -0
- package/dist/backfill-docs/doc-updater.d.ts +13 -0
- package/dist/backfill-docs/doc-updater.js +154 -0
- package/dist/backfill-docs/index.d.ts +14 -0
- package/dist/backfill-docs/index.js +233 -0
- package/dist/backfill-docs/types.d.ts +32 -0
- package/dist/backfill-docs/types.js +3 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +47 -0
- package/dist/commands/generate-docs.d.ts +24 -0
- package/dist/commands/generate-docs.js +136 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +7 -0
- package/dist/config/find-config-path.d.ts +1 -0
- package/dist/config/find-config-path.js +22 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +8 -0
- package/dist/config/installation-location.d.ts +1 -0
- package/dist/config/installation-location.js +20 -0
- package/dist/config/load-config-description.d.ts +10 -0
- package/dist/config/load-config-description.js +97 -0
- package/dist/config/load-config.d.ts +18 -0
- package/dist/config/load-config.js +30 -0
- package/dist/config/load-third-party-checks.d.ts +16 -0
- package/dist/config/load-third-party-checks.js +69 -0
- package/dist/config/resolve/index.d.ts +1 -0
- package/dist/config/resolve/index.js +6 -0
- package/dist/config/resolve/merge-fragments.d.ts +11 -0
- package/dist/config/resolve/merge-fragments.js +52 -0
- package/dist/config/resolve/read-yaml.d.ts +11 -0
- package/dist/config/resolve/read-yaml.js +205 -0
- package/dist/config/resolve/resolve-config.d.ts +8 -0
- package/dist/config/resolve/resolve-config.js +37 -0
- package/dist/config/types.d.ts +35 -0
- package/dist/config/types.js +19 -0
- package/dist/config/validation.d.ts +2 -0
- package/dist/config/validation.js +85 -0
- package/dist/file-utils.d.ts +1 -0
- package/dist/file-utils.js +17 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +160 -0
- package/dist/temp.d.ts +2 -0
- package/dist/temp.js +3 -0
- package/dist/test/test-helpers.d.ts +14 -0
- package/dist/test/test-helpers.js +119 -0
- package/package.json +45 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readYamlConfigDescription = readYamlConfigDescription;
|
|
7
|
+
const platformos_check_common_1 = require("@platformos/platformos-check-common");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const yaml_1 = require("yaml");
|
|
12
|
+
const types_1 = require("../types");
|
|
13
|
+
class UnresolvedAliasError extends Error {
|
|
14
|
+
constructor(message, alias) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'UnresolvedAliasError';
|
|
17
|
+
this.message = `YAML parsing error: Unresolved alias *${alias}.
|
|
18
|
+
Did you forget to wrap your ignore statement in quotes? '*${alias}'
|
|
19
|
+
${message}`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function parseYamlFile(absolutePath, contents) {
|
|
23
|
+
try {
|
|
24
|
+
const result = contents.trim() === '' ? {} : (0, yaml_1.parse)(contents);
|
|
25
|
+
if (!isPlainObject(result)) {
|
|
26
|
+
throw new Error(`Expecting parsed contents of config file at path '${absolutePath}' to be a plain object`);
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error instanceof Error &&
|
|
32
|
+
error.name === 'ReferenceError' &&
|
|
33
|
+
error.message.includes('Unresolved alias') &&
|
|
34
|
+
/: .*$/m.test(error.message)) {
|
|
35
|
+
const alias = /: .*$/m.exec(error.message)[0].slice(2);
|
|
36
|
+
throw new UnresolvedAliasError(error.message, alias);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Takes an absolute path, parses the yaml at that path and turns it into a
|
|
45
|
+
* ConfigFragment object.
|
|
46
|
+
*/
|
|
47
|
+
async function readYamlConfigDescription(
|
|
48
|
+
/** the absolute path to a .platformos-check.yml file */
|
|
49
|
+
absolutePath,
|
|
50
|
+
/** only the root config has `extends: platformos-check:recommended` by default, it's nothing everywhere else */
|
|
51
|
+
isRootConfig = false) {
|
|
52
|
+
const root = node_path_1.default.dirname(absolutePath);
|
|
53
|
+
const contents = await promises_1.default.readFile(absolutePath, 'utf8');
|
|
54
|
+
const yamlFile = parseYamlFile(absolutePath, contents);
|
|
55
|
+
const config = {
|
|
56
|
+
checkSettings: {},
|
|
57
|
+
ignore: [],
|
|
58
|
+
extends: [],
|
|
59
|
+
require: [],
|
|
60
|
+
};
|
|
61
|
+
if (yamlFile.root) {
|
|
62
|
+
config.root = yamlFile.root;
|
|
63
|
+
delete yamlFile.root;
|
|
64
|
+
}
|
|
65
|
+
if (yamlFile.ignore) {
|
|
66
|
+
config.ignore = asArray(yamlFile.ignore);
|
|
67
|
+
delete yamlFile.ignore;
|
|
68
|
+
}
|
|
69
|
+
if (yamlFile.require) {
|
|
70
|
+
config.require = asArray(yamlFile.require)
|
|
71
|
+
.map((pathLike) => resolvePath(root, pathLike))
|
|
72
|
+
.filter(isString);
|
|
73
|
+
delete yamlFile.require;
|
|
74
|
+
}
|
|
75
|
+
if (yamlFile.extends) {
|
|
76
|
+
config.extends = asArray(yamlFile.extends)
|
|
77
|
+
.map((pathLike) => resolveExtends(root, pathLike))
|
|
78
|
+
.filter(isString);
|
|
79
|
+
delete yamlFile.extends;
|
|
80
|
+
}
|
|
81
|
+
else if (isRootConfig) {
|
|
82
|
+
config.extends = [resolveExtends(root, 'platformos-check:recommended')];
|
|
83
|
+
}
|
|
84
|
+
if (yamlFile.context) {
|
|
85
|
+
if (platformos_check_common_1.Modes.includes(yamlFile.context)) {
|
|
86
|
+
config.context = yamlFile.context;
|
|
87
|
+
}
|
|
88
|
+
delete yamlFile.context;
|
|
89
|
+
}
|
|
90
|
+
// legacy settings that screw up assumptions
|
|
91
|
+
if (yamlFile.include_categories)
|
|
92
|
+
delete yamlFile.include_categories;
|
|
93
|
+
if (yamlFile.exclude_categories)
|
|
94
|
+
delete yamlFile.exclude_categories;
|
|
95
|
+
for (const [checkName, settings] of Object.entries(yamlFile)) {
|
|
96
|
+
if (!isPlainObject(settings)) {
|
|
97
|
+
throw new Error(`Expected a plain object value for ${checkName} but got ${typeof settings}`);
|
|
98
|
+
}
|
|
99
|
+
config.checkSettings[checkName] = resolveSettings(settings);
|
|
100
|
+
}
|
|
101
|
+
return config;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* resolves the `extends:` property of configuration files.
|
|
105
|
+
*
|
|
106
|
+
* pathLike can be any of the following:
|
|
107
|
+
* - modern identifiers:
|
|
108
|
+
* - platformos-check:all
|
|
109
|
+
* - platformos-check:recommended
|
|
110
|
+
* - platformos-check:nothing
|
|
111
|
+
* - a node_module (e.g. '@acme/platformos-check-recommended')
|
|
112
|
+
* - a relative path (e.g. '../configurations/.platformos-check.yml')
|
|
113
|
+
*
|
|
114
|
+
* @returns {string} resolved absolute path of the extended config
|
|
115
|
+
*/
|
|
116
|
+
function resolveExtends(
|
|
117
|
+
/** absolute path of the config file */
|
|
118
|
+
root,
|
|
119
|
+
/** pathLike textual value of the `extends` property in the config file */
|
|
120
|
+
pathLike) {
|
|
121
|
+
if (pathLike.startsWith('platformos-check:')) {
|
|
122
|
+
return pathLike;
|
|
123
|
+
}
|
|
124
|
+
return resolvePath(root, pathLike);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* resolves a pathLike property from the configuration file.
|
|
128
|
+
*
|
|
129
|
+
* pathLike can be any of the following:
|
|
130
|
+
* - a node_module (e.g. '@acme/platformos-check-extension')
|
|
131
|
+
* - a relative path (e.g. './lib/main.js')
|
|
132
|
+
*
|
|
133
|
+
* @returns {string} resolved absolute path of the extended config
|
|
134
|
+
*/
|
|
135
|
+
function resolvePath(
|
|
136
|
+
/** absolute path of the config file */
|
|
137
|
+
root,
|
|
138
|
+
/** pathLike textual value of the `extends` property in the config file */
|
|
139
|
+
pathLike) {
|
|
140
|
+
if (node_path_1.default.isAbsolute(pathLike)) {
|
|
141
|
+
return pathLike;
|
|
142
|
+
}
|
|
143
|
+
if (pathLike.startsWith('.')) {
|
|
144
|
+
return node_path_1.default.resolve(root, pathLike);
|
|
145
|
+
}
|
|
146
|
+
return (0, node_fs_1.realpathSync)(require.resolve(pathLike, { paths: getAncestorNodeModules(root) }));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Resolves the check settings. Will also camelCase the snake_case settings
|
|
150
|
+
* for backwards compatibility.
|
|
151
|
+
*/
|
|
152
|
+
function resolveSettings(
|
|
153
|
+
/** key value pair of settings for a check */
|
|
154
|
+
settings) {
|
|
155
|
+
const resolvedSettings = {
|
|
156
|
+
enabled: settings.enabled || true,
|
|
157
|
+
};
|
|
158
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
159
|
+
resolvedSettings[toCamelCase(key)] = value;
|
|
160
|
+
}
|
|
161
|
+
if (settings.ignore !== undefined &&
|
|
162
|
+
Array.isArray(settings.ignore) &&
|
|
163
|
+
settings.ignore.every(isString)) {
|
|
164
|
+
resolvedSettings.ignore = settings.ignore;
|
|
165
|
+
}
|
|
166
|
+
if (settings.severity !== undefined) {
|
|
167
|
+
resolvedSettings.severity = resolveSeverity(settings.severity);
|
|
168
|
+
}
|
|
169
|
+
return resolvedSettings;
|
|
170
|
+
}
|
|
171
|
+
function resolveSeverity(severity) {
|
|
172
|
+
if (isConvenienceSeverity(severity))
|
|
173
|
+
return types_1.ConvenienceSeverities[severity];
|
|
174
|
+
if (isSeverity(severity))
|
|
175
|
+
return severity;
|
|
176
|
+
throw new Error(`Unsupported severity: ${severity}. Try one of ${Object.keys(types_1.ConvenienceSeverities)}`);
|
|
177
|
+
}
|
|
178
|
+
function isConvenienceSeverity(severity) {
|
|
179
|
+
return typeof severity === 'string' && severity in types_1.ConvenienceSeverities;
|
|
180
|
+
}
|
|
181
|
+
function isSeverity(severity) {
|
|
182
|
+
return typeof severity === 'number' && severity in platformos_check_common_1.Severity;
|
|
183
|
+
}
|
|
184
|
+
function toCamelCase(maybeSnakeCaseStr) {
|
|
185
|
+
return maybeSnakeCaseStr.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
186
|
+
}
|
|
187
|
+
function isPlainObject(thing) {
|
|
188
|
+
return Object.prototype.toString.call(thing) === '[object Object]';
|
|
189
|
+
}
|
|
190
|
+
function isString(thing) {
|
|
191
|
+
return typeof thing === 'string';
|
|
192
|
+
}
|
|
193
|
+
function asArray(thing) {
|
|
194
|
+
return Array.isArray(thing) ? thing : [thing];
|
|
195
|
+
}
|
|
196
|
+
function getAncestorNodeModules(dir) {
|
|
197
|
+
const root = node_path_1.default.parse(dir).root;
|
|
198
|
+
const nodeModulesPaths = [];
|
|
199
|
+
while (dir !== root) {
|
|
200
|
+
nodeModulesPaths.push(node_path_1.default.join(dir, 'node_modules'));
|
|
201
|
+
dir = node_path_1.default.dirname(dir);
|
|
202
|
+
}
|
|
203
|
+
return nodeModulesPaths;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=read-yaml.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ConfigDescription, ModernIdentifier } from '../types';
|
|
2
|
+
import { AbsolutePath } from '../../temp';
|
|
3
|
+
/**
|
|
4
|
+
* Given a modern identifier or absolute path, fully resolves and flattens
|
|
5
|
+
* a config description. In other words, extends are all loaded and merged with
|
|
6
|
+
* the config at the configPath.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveConfig(configPath: AbsolutePath | ModernIdentifier, isRootConfig?: boolean): Promise<ConfigDescription>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveConfig = resolveConfig;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const read_yaml_1 = require("./read-yaml");
|
|
9
|
+
const merge_fragments_1 = require("./merge-fragments");
|
|
10
|
+
const types_1 = require("../types");
|
|
11
|
+
const modernConfigsPath = () => {
|
|
12
|
+
if (process.env.WEBPACK_MODE) {
|
|
13
|
+
return node_path_1.default.resolve(__dirname, './configs');
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
return node_path_1.default.resolve(__dirname, '../../../configs');
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Given a modern identifier or absolute path, fully resolves and flattens
|
|
21
|
+
* a config description. In other words, extends are all loaded and merged with
|
|
22
|
+
* the config at the configPath.
|
|
23
|
+
*/
|
|
24
|
+
async function resolveConfig(configPath, isRootConfig = false) {
|
|
25
|
+
if (isModernIdentifier(configPath)) {
|
|
26
|
+
const modernConfigPath = node_path_1.default.join(modernConfigsPath(), configPath.replace(/^platformos-check:/, '') + '.yml');
|
|
27
|
+
return resolveConfig(modernConfigPath);
|
|
28
|
+
}
|
|
29
|
+
// TODO: Add support for more file formats.
|
|
30
|
+
const current = await (0, read_yaml_1.readYamlConfigDescription)(configPath, isRootConfig);
|
|
31
|
+
const baseConfigs = await Promise.all(current.extends.map((extend) => resolveConfig(extend)));
|
|
32
|
+
return (0, merge_fragments_1.mergeFragments)(baseConfigs, current);
|
|
33
|
+
}
|
|
34
|
+
function isModernIdentifier(thing) {
|
|
35
|
+
return types_1.ModernIdentifiers.includes(thing);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=resolve-config.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ChecksSettings, Mode, Severity } from '@platformos/platformos-check-common';
|
|
2
|
+
/**
|
|
3
|
+
* The pipeline goes like this:
|
|
4
|
+
*
|
|
5
|
+
* File # the input file as a string
|
|
6
|
+
* -> ConfigFragment # an intermediate representation of the file
|
|
7
|
+
* -> ConfigFragment[] # the file and its extends
|
|
8
|
+
* -> ConfigDescription # the flattened config (no extends)
|
|
9
|
+
* -> Config # the theme check config
|
|
10
|
+
*
|
|
11
|
+
* Our goal is to support more than one config file format, so what we'll
|
|
12
|
+
* do is have one adapter per file format that outputs a ConfigFragment.
|
|
13
|
+
*
|
|
14
|
+
* Then we'll be able to merge all the config fragments, independently of
|
|
15
|
+
* which file format used.
|
|
16
|
+
*/
|
|
17
|
+
export interface ConfigFragment {
|
|
18
|
+
root?: string;
|
|
19
|
+
ignore: string[];
|
|
20
|
+
extends: string[];
|
|
21
|
+
require: string[];
|
|
22
|
+
checkSettings: ChecksSettings;
|
|
23
|
+
context?: Mode;
|
|
24
|
+
}
|
|
25
|
+
/** A ConfigDescription is a ConfigFragment that doesn't extend anything. */
|
|
26
|
+
export type ConfigDescription = Omit<ConfigFragment, 'extends' | 'context'> & {
|
|
27
|
+
extends: [];
|
|
28
|
+
context: Mode;
|
|
29
|
+
};
|
|
30
|
+
export declare const ModernIdentifiers: readonly ["platformos-check:nothing", "platformos-check:recommended", "platformos-check:all"];
|
|
31
|
+
export type ModernIdentifier = (typeof ModernIdentifiers)[number];
|
|
32
|
+
export type ConvenienceSeverity = 'error' | 'suggestion' | 'style' | 'warning' | 'info';
|
|
33
|
+
export declare const ConvenienceSeverities: {
|
|
34
|
+
[k in ConvenienceSeverity]: Severity;
|
|
35
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConvenienceSeverities = exports.ModernIdentifiers = void 0;
|
|
4
|
+
const platformos_check_common_1 = require("@platformos/platformos-check-common");
|
|
5
|
+
exports.ModernIdentifiers = [
|
|
6
|
+
'platformos-check:nothing',
|
|
7
|
+
'platformos-check:recommended',
|
|
8
|
+
'platformos-check:all',
|
|
9
|
+
];
|
|
10
|
+
exports.ConvenienceSeverities = {
|
|
11
|
+
// legacy
|
|
12
|
+
suggestion: platformos_check_common_1.Severity.WARNING,
|
|
13
|
+
style: platformos_check_common_1.Severity.INFO,
|
|
14
|
+
// the numerical values are not user friendly
|
|
15
|
+
error: platformos_check_common_1.Severity.ERROR,
|
|
16
|
+
warning: platformos_check_common_1.Severity.WARNING,
|
|
17
|
+
info: platformos_check_common_1.Severity.INFO,
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateConfig = validateConfig;
|
|
4
|
+
const node_assert_1 = require("node:assert");
|
|
5
|
+
function validateConfig(config) {
|
|
6
|
+
for (const check of config.checks) {
|
|
7
|
+
const settings = config.settings[check.meta.code];
|
|
8
|
+
(0, node_assert_1.strict)(settings, `Unexpected missing settings for ${check.meta.code} in validateConfig call`);
|
|
9
|
+
validateSettings(check.meta.code, check.meta.schema, settings, true);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function validateSettings(checkCode, schema, settings, isTopLevel) {
|
|
13
|
+
for (const key in schema) {
|
|
14
|
+
const schemaProp = schema[key];
|
|
15
|
+
const settingValue = settings[key];
|
|
16
|
+
validateValue(key, settingValue, schemaProp);
|
|
17
|
+
}
|
|
18
|
+
if (isTopLevel) {
|
|
19
|
+
validateCommonCheckSettings(settings);
|
|
20
|
+
}
|
|
21
|
+
validateSuperfluousSettings(checkCode, schema, settings);
|
|
22
|
+
}
|
|
23
|
+
function validateCommonCheckSettings(settings) {
|
|
24
|
+
const { severity, enabled, ignore } = settings;
|
|
25
|
+
severity === undefined || assertType('severity', 'number', typeof severity);
|
|
26
|
+
assertType('enabled', 'boolean', typeof enabled);
|
|
27
|
+
ignore === undefined || assertArray('ignore', ignore);
|
|
28
|
+
}
|
|
29
|
+
function validateSuperfluousSettings(checkCode, schema, settings) {
|
|
30
|
+
const commonCheckSettingsKeys = ['enabled', 'severity', 'ignore'];
|
|
31
|
+
for (const key in settings) {
|
|
32
|
+
if (!(key in schema) && !commonCheckSettingsKeys.includes(key)) {
|
|
33
|
+
console.error(`Unexpected setting: ${key} for check ${checkCode} found in configuration`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function validateValue(key, value, schemaProp) {
|
|
38
|
+
const { type, optional, defaultValue, properties, itemType } = schemaProp.options;
|
|
39
|
+
if (value === undefined) {
|
|
40
|
+
if (optional !== true && defaultValue === undefined) {
|
|
41
|
+
throw new Error(`Missing required setting: ${key}`);
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (defaultValue !== undefined && value === defaultValue) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
switch (type) {
|
|
49
|
+
case 'string':
|
|
50
|
+
case 'number':
|
|
51
|
+
case 'boolean': {
|
|
52
|
+
assertType(key, type, typeof value);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case 'object': {
|
|
56
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
57
|
+
throw new Error(`Invalid type for setting ${key}: expected object, got ${typeof value}`);
|
|
58
|
+
}
|
|
59
|
+
for (const nestedKey in properties) {
|
|
60
|
+
validateValue(nestedKey, value[nestedKey], properties[nestedKey]);
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case 'array': {
|
|
65
|
+
assertArray(key, value);
|
|
66
|
+
if (itemType) {
|
|
67
|
+
for (const item of value) {
|
|
68
|
+
validateValue(key, item, itemType);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
default: {
|
|
74
|
+
throw new Error(`Unexpected setting type: ${type}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const typeOf = (thing) => typeof thing;
|
|
79
|
+
function assertType(key, expectedType, actualType) {
|
|
80
|
+
node_assert_1.strict.strictEqual(actualType, expectedType, `Invalid type for setting ${key}: expected ${expectedType}, got ${actualType}`);
|
|
81
|
+
}
|
|
82
|
+
function assertArray(key, thing) {
|
|
83
|
+
node_assert_1.strict.ok(Array.isArray(thing), `Invalid type for setting ${key}: expected array, got ${typeof thing}`);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fileExists(path: string): Promise<boolean>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.fileExists = fileExists;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
async function fileExists(path) {
|
|
9
|
+
try {
|
|
10
|
+
await promises_1.default.stat(path);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=file-utils.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Config, GraphQLSourceCode, JSONSourceCode, LiquidSourceCode, Offense, Theme } from '@platformos/platformos-check-common';
|
|
2
|
+
import { loadConfig as resolveConfig } from './config';
|
|
3
|
+
import { NodeFileSystem } from './NodeFileSystem';
|
|
4
|
+
export * from '@platformos/platformos-check-common';
|
|
5
|
+
export * from './config/types';
|
|
6
|
+
export { NodeFileSystem };
|
|
7
|
+
export { runBackfillDocsCLI } from './backfill-docs';
|
|
8
|
+
export declare const loadConfig: typeof resolveConfig;
|
|
9
|
+
export type ThemeCheckRun = {
|
|
10
|
+
theme: Theme;
|
|
11
|
+
config: Config;
|
|
12
|
+
offenses: Offense[];
|
|
13
|
+
};
|
|
14
|
+
export declare function toSourceCode(absolutePath: string): Promise<LiquidSourceCode | JSONSourceCode | GraphQLSourceCode | undefined>;
|
|
15
|
+
export declare function check(root: string, configPath?: string): Promise<Offense[]>;
|
|
16
|
+
export declare function checkAndAutofix(root: string, configPath?: string): Promise<void>;
|
|
17
|
+
export declare function themeCheckRun(root: string, configPath?: string, log?: (message: string) => void): Promise<ThemeCheckRun>;
|
|
18
|
+
export declare function getThemeAndConfig(root: string, configPath?: string): Promise<{
|
|
19
|
+
theme: Theme;
|
|
20
|
+
config: Config;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function getTheme(config: Config): Promise<Theme>;
|
|
23
|
+
export declare function getThemeFilesPathPattern(rootUri: string): string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.loadConfig = exports.runBackfillDocsCLI = exports.NodeFileSystem = void 0;
|
|
21
|
+
exports.toSourceCode = toSourceCode;
|
|
22
|
+
exports.check = check;
|
|
23
|
+
exports.checkAndAutofix = checkAndAutofix;
|
|
24
|
+
exports.themeCheckRun = themeCheckRun;
|
|
25
|
+
exports.getThemeAndConfig = getThemeAndConfig;
|
|
26
|
+
exports.getTheme = getTheme;
|
|
27
|
+
exports.getThemeFilesPathPattern = getThemeFilesPathPattern;
|
|
28
|
+
const platformos_check_common_1 = require("@platformos/platformos-check-common");
|
|
29
|
+
const platformos_check_docs_updater_1 = require("@platformos/platformos-check-docs-updater");
|
|
30
|
+
const liquid_html_parser_1 = require("@platformos/liquid-html-parser");
|
|
31
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
32
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
33
|
+
const node_util_1 = require("node:util");
|
|
34
|
+
const vscode_uri_1 = require("vscode-uri");
|
|
35
|
+
const glob = require("glob");
|
|
36
|
+
const autofix_1 = require("./autofix");
|
|
37
|
+
const config_1 = require("./config");
|
|
38
|
+
const NodeFileSystem_1 = require("./NodeFileSystem");
|
|
39
|
+
Object.defineProperty(exports, "NodeFileSystem", { enumerable: true, get: function () { return NodeFileSystem_1.NodeFileSystem; } });
|
|
40
|
+
const node_url_1 = require("node:url");
|
|
41
|
+
const asyncGlob = (0, node_util_1.promisify)(glob);
|
|
42
|
+
__exportStar(require("@platformos/platformos-check-common"), exports);
|
|
43
|
+
__exportStar(require("./config/types"), exports);
|
|
44
|
+
var backfill_docs_1 = require("./backfill-docs");
|
|
45
|
+
Object.defineProperty(exports, "runBackfillDocsCLI", { enumerable: true, get: function () { return backfill_docs_1.runBackfillDocsCLI; } });
|
|
46
|
+
const loadConfig = async (configPath, root) => {
|
|
47
|
+
configPath ??= await (0, config_1.findConfigPath)(root);
|
|
48
|
+
return (0, config_1.loadConfig)(configPath, root);
|
|
49
|
+
};
|
|
50
|
+
exports.loadConfig = loadConfig;
|
|
51
|
+
async function toSourceCode(absolutePath) {
|
|
52
|
+
try {
|
|
53
|
+
const source = await promises_1.default.readFile(absolutePath, 'utf8');
|
|
54
|
+
return (0, platformos_check_common_1.toSourceCode)(platformos_check_common_1.path.normalize(vscode_uri_1.URI.file(absolutePath)), source);
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function check(root, configPath) {
|
|
61
|
+
const run = await themeCheckRun(root, configPath);
|
|
62
|
+
return run.offenses;
|
|
63
|
+
}
|
|
64
|
+
async function checkAndAutofix(root, configPath) {
|
|
65
|
+
const { theme, offenses } = await themeCheckRun(root, configPath);
|
|
66
|
+
await (0, autofix_1.autofix)(theme, offenses);
|
|
67
|
+
}
|
|
68
|
+
async function themeCheckRun(root, configPath, log = () => { }) {
|
|
69
|
+
const { theme, config } = await getThemeAndConfig(root, configPath);
|
|
70
|
+
const themeLiquidDocsManager = new platformos_check_docs_updater_1.ThemeLiquidDocsManager(log);
|
|
71
|
+
// This does feel a bit heavy handed, but I'm in a rush.
|
|
72
|
+
//
|
|
73
|
+
// Ultimately, I want to be able to have type safety on the parsed content
|
|
74
|
+
// of the {% schema %} tags if the schema is known to be valid. This should make
|
|
75
|
+
// {% schema %} related theme checks much easier to write than having to write visitor
|
|
76
|
+
// code and doing null checks all over the place. `ThemeBlock.Schema` is much more specific
|
|
77
|
+
// than `any` ever could be.
|
|
78
|
+
//
|
|
79
|
+
// I also want to have the option of passing down the getSectionSchema &
|
|
80
|
+
// getBlockSchema functions as dependencies. This will enable me to cache the
|
|
81
|
+
// results in the language server and avoid redoing validation between runs if
|
|
82
|
+
// we know the schema of a file that didn't change is valid.
|
|
83
|
+
//
|
|
84
|
+
// The crux of my problem is that I want to be passing down the json validation set
|
|
85
|
+
// as dependencies and not a full blown language service. But the easiest way
|
|
86
|
+
// is to have a `isValidSchema` is to have the language service do the
|
|
87
|
+
// validation of the JSON schema... We're technically going to have two
|
|
88
|
+
// JSONValidator running in theme check (node). We already have two in the
|
|
89
|
+
// language server.
|
|
90
|
+
const validator = await platformos_check_common_1.JSONValidator.create(themeLiquidDocsManager, config);
|
|
91
|
+
const isValidSchema = validator?.isValid;
|
|
92
|
+
// We can assume that all files are loaded when running themeCheckRun
|
|
93
|
+
const schemas = theme.map((source) => (0, platformos_check_common_1.toSchema)(config.context, source.uri, source, isValidSchema));
|
|
94
|
+
// prettier-ignore
|
|
95
|
+
const blockSchemas = new Map(theme.filter(source => (0, platformos_check_common_1.isBlock)(source.uri)).map((source) => [
|
|
96
|
+
node_path_1.default.basename(source.uri, '.liquid'),
|
|
97
|
+
(0, platformos_check_common_1.memo)(async () => (0, platformos_check_common_1.toSchema)(config.context, source.uri, source, isValidSchema))
|
|
98
|
+
]));
|
|
99
|
+
// prettier-ignore
|
|
100
|
+
const sectionSchemas = new Map(theme.filter(source => (0, platformos_check_common_1.isSection)(source.uri)).map((source) => [
|
|
101
|
+
node_path_1.default.basename(source.uri, '.liquid'),
|
|
102
|
+
(0, platformos_check_common_1.memo)(async () => (0, platformos_check_common_1.toSchema)(config.context, source.uri, source, isValidSchema))
|
|
103
|
+
]));
|
|
104
|
+
const docDefinitions = new Map(theme.map((file) => [
|
|
105
|
+
node_path_1.default.relative(vscode_uri_1.URI.file(root).toString(), file.uri),
|
|
106
|
+
(0, platformos_check_common_1.memo)(async () => {
|
|
107
|
+
const ast = file.ast;
|
|
108
|
+
if (!(0, liquid_html_parser_1.isLiquidHtmlNode)(ast)) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
if (!(0, platformos_check_common_1.filePathSupportsLiquidDoc)(file.uri)) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
return (0, platformos_check_common_1.extractDocDefinition)(file.uri, ast);
|
|
115
|
+
}),
|
|
116
|
+
]));
|
|
117
|
+
const offenses = await (0, platformos_check_common_1.check)(theme, config, {
|
|
118
|
+
fs: NodeFileSystem_1.NodeFileSystem,
|
|
119
|
+
themeDocset: themeLiquidDocsManager,
|
|
120
|
+
jsonValidationSet: themeLiquidDocsManager,
|
|
121
|
+
// This is kind of gross, but we want those things to be lazy and called by name so...
|
|
122
|
+
// In the language server, this is memo'ed in DocumentManager, but we don't have that kind
|
|
123
|
+
// of luxury in CLI-mode.
|
|
124
|
+
getSectionSchema: async (name) => sectionSchemas.get(name)?.(),
|
|
125
|
+
getBlockSchema: async (name) => blockSchemas.get(name)?.(),
|
|
126
|
+
getAppBlockSchema: async (name) => blockSchemas.get(name)?.(), // cheating... but TODO
|
|
127
|
+
getDocDefinition: async (relativePath) => docDefinitions.get(relativePath)?.(),
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
theme,
|
|
131
|
+
config,
|
|
132
|
+
offenses,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function getThemeAndConfig(root, configPath) {
|
|
136
|
+
const config = await (0, exports.loadConfig)(configPath, root);
|
|
137
|
+
const theme = await getTheme(config);
|
|
138
|
+
return {
|
|
139
|
+
theme,
|
|
140
|
+
config,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function getTheme(config) {
|
|
144
|
+
// On windows machines - the separator provided by path.join is '\'
|
|
145
|
+
// however the glob function fails silently since '\' is used to escape glob charater
|
|
146
|
+
// as mentioned in the documentation of node-glob
|
|
147
|
+
// the path is normalised and '\' are replaced with '/' and then passed to the glob function
|
|
148
|
+
let normalizedGlob = getThemeFilesPathPattern(config.rootUri);
|
|
149
|
+
const paths = await asyncGlob(normalizedGlob, { absolute: true }).then((result) =>
|
|
150
|
+
// Global ignored paths should not be part of the theme
|
|
151
|
+
result.filter((filePath) => !(0, platformos_check_common_1.isIgnored)(filePath, config)));
|
|
152
|
+
const sourceCodes = await Promise.all(paths.map(toSourceCode));
|
|
153
|
+
return sourceCodes.filter((x) => x !== undefined);
|
|
154
|
+
}
|
|
155
|
+
function getThemeFilesPathPattern(rootUri) {
|
|
156
|
+
return node_path_1.default
|
|
157
|
+
.normalize(node_path_1.default.join((0, node_url_1.fileURLToPath)(rootUri), '**/*.{liquid,json,graphql}'))
|
|
158
|
+
.replace(/\\/g, '/');
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=index.js.map
|
package/dist/temp.d.ts
ADDED
package/dist/temp.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function makeTmpFolder(): Promise<string>;
|
|
2
|
+
export declare function removeTmpFolder(tempDir: string): Promise<void>;
|
|
3
|
+
export declare function createMockConfigFile(tempDir: string, contents?: string, relativePath?: string): Promise<string>;
|
|
4
|
+
export declare const mockNodeModuleCheck = "\n const NodeModuleCheck = {\n meta: {\n name: 'NodeModuleCheck',\n code: 'NodeModuleCheck',\n docs: { description: '...' },\n schema: {},\n severity: 0,\n targets: [],\n type: 'LiquidHtml',\n },\n create() {\n return {};\n },\n };\n\n exports.checks = [\n NodeModuleCheck,\n ];\n";
|
|
5
|
+
export declare function createMockNodeModule(tempDir: string, moduleName: string, moduleContent?: string): Promise<string>;
|
|
6
|
+
export type Tree = {
|
|
7
|
+
[k in string]: Tree | string;
|
|
8
|
+
};
|
|
9
|
+
export interface Workspace {
|
|
10
|
+
rootUri: string;
|
|
11
|
+
uri(relativePath: string): string;
|
|
12
|
+
clean(): Promise<any>;
|
|
13
|
+
}
|
|
14
|
+
export declare function makeTempWorkspace(structure: Tree): Promise<Workspace>;
|