@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.
Files changed (57) hide show
  1. package/README.md +9 -0
  2. package/configs/all.yml +214 -0
  3. package/configs/nothing.yml +3 -0
  4. package/configs/recommended.yml +192 -0
  5. package/dist/NodeFileSystem.d.ts +2 -0
  6. package/dist/NodeFileSystem.js +37 -0
  7. package/dist/autofix.d.ts +6 -0
  8. package/dist/autofix.js +18 -0
  9. package/dist/backfill-docs/argument-collector.d.ts +6 -0
  10. package/dist/backfill-docs/argument-collector.js +167 -0
  11. package/dist/backfill-docs/doc-generator.d.ts +18 -0
  12. package/dist/backfill-docs/doc-generator.js +31 -0
  13. package/dist/backfill-docs/doc-updater.d.ts +13 -0
  14. package/dist/backfill-docs/doc-updater.js +154 -0
  15. package/dist/backfill-docs/index.d.ts +14 -0
  16. package/dist/backfill-docs/index.js +233 -0
  17. package/dist/backfill-docs/types.d.ts +32 -0
  18. package/dist/backfill-docs/types.js +3 -0
  19. package/dist/cli.d.ts +1 -0
  20. package/dist/cli.js +47 -0
  21. package/dist/commands/generate-docs.d.ts +24 -0
  22. package/dist/commands/generate-docs.js +136 -0
  23. package/dist/commands/index.d.ts +1 -0
  24. package/dist/commands/index.js +7 -0
  25. package/dist/config/find-config-path.d.ts +1 -0
  26. package/dist/config/find-config-path.js +22 -0
  27. package/dist/config/index.d.ts +2 -0
  28. package/dist/config/index.js +8 -0
  29. package/dist/config/installation-location.d.ts +1 -0
  30. package/dist/config/installation-location.js +20 -0
  31. package/dist/config/load-config-description.d.ts +10 -0
  32. package/dist/config/load-config-description.js +97 -0
  33. package/dist/config/load-config.d.ts +18 -0
  34. package/dist/config/load-config.js +30 -0
  35. package/dist/config/load-third-party-checks.d.ts +16 -0
  36. package/dist/config/load-third-party-checks.js +69 -0
  37. package/dist/config/resolve/index.d.ts +1 -0
  38. package/dist/config/resolve/index.js +6 -0
  39. package/dist/config/resolve/merge-fragments.d.ts +11 -0
  40. package/dist/config/resolve/merge-fragments.js +52 -0
  41. package/dist/config/resolve/read-yaml.d.ts +11 -0
  42. package/dist/config/resolve/read-yaml.js +205 -0
  43. package/dist/config/resolve/resolve-config.d.ts +8 -0
  44. package/dist/config/resolve/resolve-config.js +37 -0
  45. package/dist/config/types.d.ts +35 -0
  46. package/dist/config/types.js +19 -0
  47. package/dist/config/validation.d.ts +2 -0
  48. package/dist/config/validation.js +85 -0
  49. package/dist/file-utils.d.ts +1 -0
  50. package/dist/file-utils.js +17 -0
  51. package/dist/index.d.ts +23 -0
  52. package/dist/index.js +160 -0
  53. package/dist/temp.d.ts +2 -0
  54. package/dist/temp.js +3 -0
  55. package/dist/test/test-helpers.d.ts +14 -0
  56. package/dist/test/test-helpers.js +119 -0
  57. 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,2 @@
1
+ import { Config } from '@platformos/platformos-check-common';
2
+ export declare function validateConfig(config: Config): void;
@@ -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
@@ -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
@@ -0,0 +1,2 @@
1
+ /** Temporarily keeping this. Should remove with #532 */
2
+ export type AbsolutePath = string;
package/dist/temp.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=temp.js.map
@@ -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>;