@gtkx/config 2.0.0-beta.10 → 2.0.0-beta.11
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 +11 -169
- package/dist/config-dependencies.d.ts.map +1 -1
- package/dist/config-dependencies.js +3 -2
- package/dist/config-dependencies.js.map +1 -1
- package/dist/config-reloader.d.ts +8 -0
- package/dist/config-reloader.d.ts.map +1 -0
- package/dist/config-reloader.js +38 -0
- package/dist/config-reloader.js.map +1 -0
- package/dist/config-source.d.ts +7 -0
- package/dist/config-source.d.ts.map +1 -0
- package/dist/config-source.js +17 -0
- package/dist/config-source.js.map +1 -0
- package/dist/config.d.ts +32 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -10
- package/dist/config.js.map +1 -1
- package/dist/deploy.d.ts +3 -7
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +1 -2
- package/dist/deploy.js.map +1 -1
- package/dist/internal.d.ts +4 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +4 -2
- package/dist/internal.js.map +1 -1
- package/dist/loader.d.ts +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +3 -15
- package/dist/loader.js.map +1 -1
- package/dist/node-version.d.ts +2 -1
- package/dist/node-version.d.ts.map +1 -1
- package/dist/node-version.js +2 -1
- package/dist/node-version.js.map +1 -1
- package/dist/static-dependencies.d.ts +3 -0
- package/dist/static-dependencies.d.ts.map +1 -0
- package/dist/static-dependencies.js +350 -0
- package/dist/static-dependencies.js.map +1 -0
- package/dist/virtual.d.ts.map +1 -1
- package/dist/virtual.js +0 -2
- package/dist/virtual.js.map +1 -1
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js.map +1 -1
- package/package.json +20 -6
- package/src/config-dependencies.ts +4 -2
- package/src/config-reloader.ts +54 -0
- package/src/config-source.ts +25 -0
- package/src/config.ts +22 -14
- package/src/deploy.ts +2 -3
- package/src/internal.ts +4 -1
- package/src/loader.ts +4 -24
- package/src/node-version.ts +2 -1
- package/src/static-dependencies.ts +464 -0
- package/src/virtual.ts +0 -2
- package/src/vite-plugin.ts +0 -2
package/src/config.ts
CHANGED
|
@@ -25,13 +25,11 @@ type ResolvedReactCompilerOptions = ReactCompilerOptions & {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
* to bind and where to find them, the GApplication id, per-element configuration, the React
|
|
30
|
-
* Compiler, codegen, and user event signal settings, and the `agents` and `mcp` blocks controlling
|
|
31
|
-
* what coding agents are given.
|
|
28
|
+
* GTKX project configuration authored in `gtkx.config.ts` and validated when loaded.
|
|
32
29
|
*/
|
|
33
30
|
type Config = z.infer<typeof configSchema>;
|
|
34
31
|
type ModuleExport = z.infer<typeof moduleExportSchema>;
|
|
32
|
+
type ElementPropsExport = z.infer<typeof elementPropsSchema>;
|
|
35
33
|
type ElementConfigEntry = z.infer<typeof elementConfigSchema>;
|
|
36
34
|
|
|
37
35
|
type McpSettings = {
|
|
@@ -80,7 +78,7 @@ const applicationIdSchema = z
|
|
|
80
78
|
|
|
81
79
|
const reactCompilerSchema = z.union([
|
|
82
80
|
z.boolean(),
|
|
83
|
-
z.
|
|
81
|
+
z.strictObject({
|
|
84
82
|
compilationMode: z.enum(COMPILATION_MODES).optional(),
|
|
85
83
|
panicThreshold: z.enum(PANIC_THRESHOLDS).optional(),
|
|
86
84
|
}),
|
|
@@ -97,7 +95,7 @@ const userEventSignalsSchema = z.record(
|
|
|
97
95
|
{ error: "must be a record of GLib type names to signal name arrays" },
|
|
98
96
|
);
|
|
99
97
|
|
|
100
|
-
const moduleExportSchema = z.
|
|
98
|
+
const moduleExportSchema = z.strictObject(
|
|
101
99
|
{
|
|
102
100
|
module: z.string({ error: "must be a module specifier" }).min(1, { error: "must be a module specifier" }),
|
|
103
101
|
export: z.string({ error: "must be an export name" }).min(1, { error: "must be an export name" }),
|
|
@@ -105,10 +103,16 @@ const moduleExportSchema = z.object(
|
|
|
105
103
|
{ error: "must be a { module, export } object" },
|
|
106
104
|
);
|
|
107
105
|
|
|
108
|
-
const
|
|
106
|
+
const elementPropsSchema = moduleExportSchema.extend({
|
|
107
|
+
composition: z.enum(["factory", "intersection"]).optional(),
|
|
108
|
+
constructOnly: z.array(z.string()).optional(),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const elementConfigSchema = z.strictObject({
|
|
109
112
|
component: moduleExportSchema.optional(),
|
|
110
|
-
props:
|
|
113
|
+
props: elementPropsSchema.optional(),
|
|
111
114
|
isLazy: z.boolean({ error: "must be a boolean" }).optional(),
|
|
115
|
+
acceptedChildTypes: z.array(z.string()).optional(),
|
|
112
116
|
omittedProps: z
|
|
113
117
|
.array(
|
|
114
118
|
z.string({ error: "must be a non-empty property name" }).min(1, {
|
|
@@ -119,7 +123,7 @@ const elementConfigSchema = z.object({
|
|
|
119
123
|
.optional(),
|
|
120
124
|
});
|
|
121
125
|
|
|
122
|
-
const elementsSchema = z.
|
|
126
|
+
const elementsSchema = z.strictObject({
|
|
123
127
|
behaviors: z
|
|
124
128
|
.string({ error: "must be a path to a module exporting element behaviors" })
|
|
125
129
|
.min(1, { error: "must be a path to a module exporting element behaviors" })
|
|
@@ -127,12 +131,12 @@ const elementsSchema = z.object({
|
|
|
127
131
|
config: z.record(z.string(), elementConfigSchema).optional(),
|
|
128
132
|
});
|
|
129
133
|
|
|
130
|
-
const agentsSchema = z.
|
|
134
|
+
const agentsSchema = z.strictObject({
|
|
131
135
|
rules: z.boolean({ error: "must be a boolean" }).optional(),
|
|
132
136
|
reference: z.boolean({ error: "must be a boolean" }).optional(),
|
|
133
137
|
});
|
|
134
138
|
|
|
135
|
-
const mcpSchema = z.
|
|
139
|
+
const mcpSchema = z.strictObject({
|
|
136
140
|
tools: z
|
|
137
141
|
.array(z.string({ error: "must be a tool name pattern" }).min(1, { error: "must be a tool name pattern" }), {
|
|
138
142
|
error: "must be an array of tool name patterns",
|
|
@@ -153,7 +157,7 @@ const graduatedFutureSchema = z
|
|
|
153
157
|
})
|
|
154
158
|
.strict();
|
|
155
159
|
|
|
156
|
-
const deprecationsSchema = z.
|
|
160
|
+
const deprecationsSchema = z.strictObject({
|
|
157
161
|
silence: z
|
|
158
162
|
.array(z.never({ error: "does not name a current deprecation" }), {
|
|
159
163
|
error: "must be an array of current deprecation ids",
|
|
@@ -162,7 +166,7 @@ const deprecationsSchema = z.object({
|
|
|
162
166
|
});
|
|
163
167
|
|
|
164
168
|
/** Schema every `gtkx.config.ts` is validated against, and the source of the {@link Config} type. */
|
|
165
|
-
const configSchema = z.
|
|
169
|
+
const configSchema = z.strictObject({
|
|
166
170
|
libraries: librariesSchema.optional(),
|
|
167
171
|
girPath: z.array(z.string(), { error: "must be an array of strings if provided" }).optional(),
|
|
168
172
|
applicationId: applicationIdSchema,
|
|
@@ -251,9 +255,12 @@ const elementEntryValues = <T>(
|
|
|
251
255
|
const resolveElementComponents = (elements: Config["elements"]): Record<string, ModuleExport> =>
|
|
252
256
|
elementEntryValues(elements, (entry) => entry.component);
|
|
253
257
|
|
|
254
|
-
const resolveElementProps = (elements: Config["elements"]): Record<string,
|
|
258
|
+
const resolveElementProps = (elements: Config["elements"]): Record<string, ElementPropsExport> =>
|
|
255
259
|
elementEntryValues(elements, (entry) => entry.props);
|
|
256
260
|
|
|
261
|
+
const resolveAcceptedChildTypes = (elements: Config["elements"]): Record<string, string[]> =>
|
|
262
|
+
elementEntryValues(elements, (entry) => entry.acceptedChildTypes);
|
|
263
|
+
|
|
257
264
|
const resolveOmittedProps = (elements: Config["elements"]): Record<string, string[]> =>
|
|
258
265
|
elementEntryValues(elements, (entry) => entry.omittedProps);
|
|
259
266
|
|
|
@@ -285,6 +292,7 @@ export {
|
|
|
285
292
|
resolveLazyElements,
|
|
286
293
|
resolveElementComponents,
|
|
287
294
|
resolveElementProps,
|
|
295
|
+
resolveAcceptedChildTypes,
|
|
288
296
|
resolveMcpSettings,
|
|
289
297
|
resolveOmittedProps,
|
|
290
298
|
resolveConfig,
|
package/src/deploy.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
url,
|
|
11
11
|
} from "./schema-text.ts";
|
|
12
12
|
|
|
13
|
-
const APPIMAGE_COMPRESSIONS = ["gzip", "xz", "zstd"] as const;
|
|
14
13
|
const DEB_COMPRESSIONS = ["gzip", "none", "xz", "zstd"] as const;
|
|
15
14
|
const DEB_SIGN_METHODS = ["debsign", "dpkg-sig"] as const;
|
|
16
15
|
const DEB_SIGN_TYPES = ["archive", "maint", "origin"] as const;
|
|
@@ -160,7 +159,7 @@ type DeployNodeOptions = Partial<
|
|
|
160
159
|
Record<"path", string | undefined> &
|
|
161
160
|
Record<"shouldStrip" | "shouldUseCompileCache", boolean | undefined>
|
|
162
161
|
> & {
|
|
163
|
-
/** Expected Node.js version. Downloaded runtimes default to exactly 26.
|
|
162
|
+
/** Expected Node.js version. Downloaded runtimes default to exactly 26.8.2 when omitted. */
|
|
164
163
|
version?: string | undefined;
|
|
165
164
|
};
|
|
166
165
|
const nodeRuntimeSchema: z.ZodType<DeployNodeOptions> = z.strictObject({
|
|
@@ -196,7 +195,7 @@ const rpmSchema = z.strictObject({
|
|
|
196
195
|
|
|
197
196
|
const appimageSchema = z.strictObject({
|
|
198
197
|
fileName: text("must be an output file name").optional(),
|
|
199
|
-
compression: z.
|
|
198
|
+
compression: z.literal("zstd", { error: "must be zstd" }).optional(),
|
|
200
199
|
updateInformation: text("must be an AppImage update information string").optional(),
|
|
201
200
|
runtimeFile: text("must be a path to an AppImage runtime file").optional(),
|
|
202
201
|
});
|
package/src/internal.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { configDependenciesFor } from "./config-dependencies.ts";
|
|
2
|
+
export { createConfigReloader } from "./config-reloader.ts";
|
|
2
3
|
export type { McpSettings, ResolvedReactCompilerOptions } from "./config.ts";
|
|
3
4
|
export {
|
|
4
5
|
APPLICATION_ID_MAX_LENGTH,
|
|
5
6
|
isAgentReferenceEnabled,
|
|
6
7
|
isAgentRulesEnabled,
|
|
7
8
|
isValidApplicationId,
|
|
9
|
+
resolveAcceptedChildTypes,
|
|
8
10
|
resolveElementComponents,
|
|
9
11
|
resolveElementProps,
|
|
10
12
|
resolveLazyElements,
|
|
@@ -12,6 +14,7 @@ export {
|
|
|
12
14
|
resolveOmittedProps,
|
|
13
15
|
} from "./config.ts";
|
|
14
16
|
export { createConfigLoader } from "./loader.ts";
|
|
15
|
-
export { assertSupportedNodeVersion, MINIMUM_NODE_VERSION } from "./node-version.ts";
|
|
17
|
+
export { assertSupportedNodeVersion, DEFAULT_NODE_VERSION, MINIMUM_NODE_VERSION } from "./node-version.ts";
|
|
16
18
|
export { resourceBasePath } from "./resource-base-path.ts";
|
|
17
19
|
export { viteProjectRoot } from "./vite-root.ts";
|
|
20
|
+
export { SUPPORTED_EXTENSIONS as CONFIG_EXTENSIONS } from "c12";
|
package/src/loader.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { isPathInside, warn } from "@gtkx/utils";
|
|
2
2
|
import { loadConfig as loadConfigFile } from "c12";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import {
|
|
4
|
+
import { relative, resolve } from "node:path";
|
|
5
5
|
import {
|
|
6
6
|
captureConfigDependencies,
|
|
7
7
|
setConfigDependencies,
|
|
8
8
|
transformConfigModule,
|
|
9
9
|
} from "./config-dependencies.ts";
|
|
10
10
|
import { missingConfigFileError } from "./config-error.ts";
|
|
11
|
+
import { type ConfigResolutionOptions, localConfigSourcePath } from "./config-source.ts";
|
|
11
12
|
import {
|
|
12
13
|
type Config,
|
|
13
14
|
graduatedFutureKeys,
|
|
@@ -47,28 +48,6 @@ type ConfigLoader = {
|
|
|
47
48
|
const GRADUATED_FUTURE_ENV = "GTKX_GRADUATED_FUTURE_SHOWN";
|
|
48
49
|
const graduatedFutureWarnings: Map<string, string> = new Map();
|
|
49
50
|
|
|
50
|
-
type ConfigResolutionOptions = { configFile?: string | undefined; cwd?: string | undefined };
|
|
51
|
-
|
|
52
|
-
const isLocalConfigSource = (source: string): boolean => source.startsWith(".") || isAbsolute(source);
|
|
53
|
-
|
|
54
|
-
const isDirectoryConfigSource = (source: string): boolean => {
|
|
55
|
-
const extension = extname(source);
|
|
56
|
-
|
|
57
|
-
return extension.length === 0 || extension === basename(source);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const localConfigSourcePath = (source: string, options: ConfigResolutionOptions): string | undefined => {
|
|
61
|
-
if (source === "." || !isLocalConfigSource(source)) {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const cwd = options.cwd ?? process.cwd();
|
|
66
|
-
|
|
67
|
-
return isDirectoryConfigSource(source)
|
|
68
|
-
? resolve(cwd, source, options.configFile ?? "gtkx.config")
|
|
69
|
-
: resolve(cwd, source);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
51
|
const rejectMissingLocalConfig = (source: string, options: ConfigResolutionOptions): undefined => {
|
|
73
52
|
const path = localConfigSourcePath(source, options);
|
|
74
53
|
|
|
@@ -138,6 +117,7 @@ const loadConfig = async (cwd: string, options: LoadConfigOptions = {}): Promise
|
|
|
138
117
|
rcFile: false,
|
|
139
118
|
globalRc: false,
|
|
140
119
|
packageJson: false,
|
|
120
|
+
omit$Keys: true,
|
|
141
121
|
context: { mode: options.mode },
|
|
142
122
|
jitiOptions: { fsCache: false, transform: transformConfigModule },
|
|
143
123
|
resolve: rejectMissingLocalConfig,
|
|
@@ -198,4 +178,4 @@ const createConfigLoader = (options: LoadConfigOptions = {}): ConfigLoader => {
|
|
|
198
178
|
};
|
|
199
179
|
};
|
|
200
180
|
|
|
201
|
-
export { loadConfig, createConfigLoader, type LoadedConfig, type ConfigLoader };
|
|
181
|
+
export { loadConfig, createConfigLoader, type LoadedConfig, type ConfigLoader, type LoadConfigOptions };
|
package/src/node-version.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const MINIMUM_NODE_MAJOR = 26;
|
|
2
2
|
const MINIMUM_NODE_MINOR = 7;
|
|
3
3
|
const MINIMUM_NODE_VERSION = "26.7.0";
|
|
4
|
+
const DEFAULT_NODE_VERSION = "26.8.2";
|
|
4
5
|
|
|
5
6
|
const assertSupportedNodeVersion = (): void => {
|
|
6
7
|
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
|
@@ -13,4 +14,4 @@ const assertSupportedNodeVersion = (): void => {
|
|
|
13
14
|
}
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
export { assertSupportedNodeVersion, MINIMUM_NODE_VERSION };
|
|
17
|
+
export { assertSupportedNodeVersion, DEFAULT_NODE_VERSION, MINIMUM_NODE_VERSION };
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { parseSync, traverse, types } from "@babel/core";
|
|
2
|
+
import { parseJSON, parseJSON5, parseJSONC, parseTOML, parseYAML } from "confbox";
|
|
3
|
+
import { createJiti, type Jiti } from "jiti";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { basename, dirname, extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { localConfigSourcePath } from "./config-source.ts";
|
|
8
|
+
|
|
9
|
+
const MODULE_EXTENSIONS: ReadonlySet<string> = new Set([
|
|
10
|
+
".cjs",
|
|
11
|
+
".cts",
|
|
12
|
+
".js",
|
|
13
|
+
".mjs",
|
|
14
|
+
".mts",
|
|
15
|
+
".ts",
|
|
16
|
+
]);
|
|
17
|
+
const RESOLVABLE_EXTENSIONS = [
|
|
18
|
+
".js",
|
|
19
|
+
".ts",
|
|
20
|
+
".mjs",
|
|
21
|
+
".cjs",
|
|
22
|
+
".mts",
|
|
23
|
+
".cts",
|
|
24
|
+
".json",
|
|
25
|
+
".jsonc",
|
|
26
|
+
".json5",
|
|
27
|
+
".yaml",
|
|
28
|
+
".yml",
|
|
29
|
+
".toml",
|
|
30
|
+
];
|
|
31
|
+
const JAVASCRIPT_FALLBACKS: Readonly<Record<string, string[]>> = {
|
|
32
|
+
".cjs": [".cts"],
|
|
33
|
+
".js": [".ts"],
|
|
34
|
+
".jsx": [".tsx"],
|
|
35
|
+
".mjs": [".mts"],
|
|
36
|
+
};
|
|
37
|
+
const DATA_PARSERS: Readonly<Record<string, (source: string) => unknown>> = {
|
|
38
|
+
".json": parseJSON,
|
|
39
|
+
".json5": parseJSON5,
|
|
40
|
+
".jsonc": parseJSONC,
|
|
41
|
+
".toml": parseTOML,
|
|
42
|
+
".yaml": parseYAML,
|
|
43
|
+
".yml": parseYAML,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type PackageImports = { file: string; imports: Record<string, unknown>; root: string };
|
|
47
|
+
type ModuleSources = { all: string[]; extended: string[] };
|
|
48
|
+
|
|
49
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
50
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51
|
+
|
|
52
|
+
const parserPlugins = (path: string): ("decorators" | "jsx" | "typescript")[] => {
|
|
53
|
+
const extension = extname(path).toLowerCase();
|
|
54
|
+
|
|
55
|
+
return [
|
|
56
|
+
"decorators",
|
|
57
|
+
...([".cts", ".mts", ".ts"].includes(extension) ? ["typescript" as const] : []),
|
|
58
|
+
];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function expressionSources(value: types.Expression): string[] {
|
|
62
|
+
if (types.isStringLiteral(value)) {
|
|
63
|
+
return [value.value];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (types.isArrayExpression(value)) {
|
|
67
|
+
return value.elements.flatMap((element) =>
|
|
68
|
+
types.isExpression(element) ? expressionSources(element) : []);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return types.isObjectExpression(value)
|
|
72
|
+
? value.properties.flatMap((property) => sourcePropertySources(property))
|
|
73
|
+
: [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const isNamedProperty = (property: types.ObjectProperty, name: string): boolean =>
|
|
77
|
+
(types.isIdentifier(property.key) && property.key.name === name) ||
|
|
78
|
+
(types.isStringLiteral(property.key) && property.key.value === name);
|
|
79
|
+
|
|
80
|
+
function sourcePropertySources(
|
|
81
|
+
property: types.ObjectMethod | types.ObjectProperty | types.SpreadElement,
|
|
82
|
+
): string[] {
|
|
83
|
+
return types.isObjectProperty(property) &&
|
|
84
|
+
isNamedProperty(property, "source") &&
|
|
85
|
+
types.isExpression(property.value)
|
|
86
|
+
? expressionSources(property.value)
|
|
87
|
+
: [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const extendsSources = (value: types.Expression): string[] => expressionSources(value);
|
|
91
|
+
|
|
92
|
+
const isExtendsProperty = (property: types.ObjectProperty): boolean =>
|
|
93
|
+
isNamedProperty(property, "extends");
|
|
94
|
+
|
|
95
|
+
type ParsedModule = NonNullable<ReturnType<typeof parseSync>>;
|
|
96
|
+
|
|
97
|
+
const parseModule = (path: string): ParsedModule | undefined => {
|
|
98
|
+
try {
|
|
99
|
+
return parseSync(readFileSync(path, "utf8"), {
|
|
100
|
+
ast: true,
|
|
101
|
+
babelrc: false,
|
|
102
|
+
configFile: false,
|
|
103
|
+
filename: path,
|
|
104
|
+
parserOpts: { allowReturnOutsideFunction: true, plugins: parserPlugins(path) },
|
|
105
|
+
sourceType: "unambiguous",
|
|
106
|
+
}) ?? undefined;
|
|
107
|
+
} catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const addSource = (sources: Set<string>, source: types.StringLiteral | null | undefined): void => {
|
|
113
|
+
if (source !== null && source !== undefined) {
|
|
114
|
+
sources.add(source.value);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const requireSource = (call: types.CallExpression): types.StringLiteral | undefined =>
|
|
119
|
+
types.isIdentifier(call.callee, { name: "require" }) && types.isStringLiteral(call.arguments[0])
|
|
120
|
+
? call.arguments[0]
|
|
121
|
+
: undefined;
|
|
122
|
+
|
|
123
|
+
const importEqualsSource = (declaration: types.TSImportEqualsDeclaration): types.StringLiteral | undefined => {
|
|
124
|
+
const { moduleReference } = declaration;
|
|
125
|
+
|
|
126
|
+
return types.isTSExternalModuleReference(moduleReference) ? moduleReference.expression : undefined;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const propertySources = (property: types.ObjectProperty): string[] =>
|
|
130
|
+
isExtendsProperty(property) && types.isExpression(property.value)
|
|
131
|
+
? extendsSources(property.value)
|
|
132
|
+
: [];
|
|
133
|
+
|
|
134
|
+
const collectModuleSources = (ast: ParsedModule): ModuleSources => {
|
|
135
|
+
const sources: Set<string> = new Set();
|
|
136
|
+
const extended: Set<string> = new Set();
|
|
137
|
+
|
|
138
|
+
traverse(ast, {
|
|
139
|
+
CallExpression: (nodePath) => {
|
|
140
|
+
addSource(sources, requireSource(nodePath.node));
|
|
141
|
+
},
|
|
142
|
+
ExportAllDeclaration: (nodePath) => {
|
|
143
|
+
addSource(sources, nodePath.node.source);
|
|
144
|
+
},
|
|
145
|
+
ExportNamedDeclaration: (nodePath) => {
|
|
146
|
+
addSource(sources, nodePath.node.source);
|
|
147
|
+
},
|
|
148
|
+
ImportDeclaration: (nodePath) => {
|
|
149
|
+
addSource(sources, nodePath.node.source);
|
|
150
|
+
},
|
|
151
|
+
ImportExpression: (nodePath) => {
|
|
152
|
+
const { source } = nodePath.node;
|
|
153
|
+
addSource(sources, types.isStringLiteral(source) ? source : undefined);
|
|
154
|
+
},
|
|
155
|
+
ObjectProperty: (nodePath) => {
|
|
156
|
+
for (const source of propertySources(nodePath.node)) {
|
|
157
|
+
sources.add(source);
|
|
158
|
+
extended.add(source);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
TSImportEqualsDeclaration: (nodePath) => {
|
|
162
|
+
addSource(sources, importEqualsSource(nodePath.node));
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return { all: [...sources], extended: [...extended] };
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const moduleSources = (path: string): ModuleSources => {
|
|
170
|
+
const ast = parseModule(path);
|
|
171
|
+
|
|
172
|
+
return ast === undefined ? { all: [], extended: [] } : collectModuleSources(ast);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
function parsedExtendsSources(value: unknown): string[] {
|
|
176
|
+
if (typeof value === "string") {
|
|
177
|
+
return [value];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (Array.isArray(value)) {
|
|
181
|
+
return value.flatMap((item) => parsedExtendsSources(item));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return objectExtendsSources(value);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const objectExtendsSources = (value: unknown): string[] => {
|
|
188
|
+
if (typeof value !== "object" || value === null) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return "source" in value ? parsedExtendsSources(value.source) : [];
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const configExtendsSources = (value: unknown): string[] => {
|
|
196
|
+
if (typeof value !== "object" || value === null) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return "extends" in value ? parsedExtendsSources(value.extends) : [];
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const dataSources = (path: string): string[] => {
|
|
204
|
+
const parser = DATA_PARSERS[extname(path).toLowerCase()];
|
|
205
|
+
|
|
206
|
+
if (parser === undefined) {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
return configExtendsSources(parser(readFileSync(path, "utf8")));
|
|
212
|
+
} catch {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const extensionCandidates = (path: string): string[] => {
|
|
218
|
+
const extension = extname(path).toLowerCase();
|
|
219
|
+
|
|
220
|
+
if (extension.length === 0) {
|
|
221
|
+
return RESOLVABLE_EXTENSIONS.map((candidate) => `${path}${candidate}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const stem = path.slice(0, -extension.length);
|
|
225
|
+
|
|
226
|
+
return (JAVASCRIPT_FALLBACKS[extension] ?? []).map((candidate) => `${stem}${candidate}`);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const targetCandidates = (target: string, configName: string): string[] => {
|
|
230
|
+
const extension = extname(target);
|
|
231
|
+
|
|
232
|
+
if (extension.length > 0) {
|
|
233
|
+
return [target, ...extensionCandidates(target)];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return [
|
|
237
|
+
target,
|
|
238
|
+
...extensionCandidates(target),
|
|
239
|
+
join(target, configName),
|
|
240
|
+
...RESOLVABLE_EXTENSIONS.map((candidate) => join(target, `index${candidate}`)),
|
|
241
|
+
];
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const localSourceTarget = (source: string, importer: string): string | undefined => {
|
|
245
|
+
const clean = source.split(/[?#]/u, 1)[0] ?? "";
|
|
246
|
+
|
|
247
|
+
if (clean.startsWith("file:")) {
|
|
248
|
+
return fileURLToPath(clean);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!isAbsolute(clean) && !clean.startsWith(".")) {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return resolve(dirname(importer), clean);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const unresolvedCandidates = (source: string, importer: string, configName: string): string[] => {
|
|
259
|
+
const target = localSourceTarget(source, importer);
|
|
260
|
+
|
|
261
|
+
if (target === undefined) {
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return targetCandidates(target, configName);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const nearestPackageFile = (importer: string): string | undefined => {
|
|
269
|
+
let directory = dirname(importer);
|
|
270
|
+
let previous = "";
|
|
271
|
+
|
|
272
|
+
while (directory !== previous) {
|
|
273
|
+
const candidate = join(directory, "package.json");
|
|
274
|
+
|
|
275
|
+
if (existsSync(candidate)) {
|
|
276
|
+
return candidate;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
previous = directory;
|
|
280
|
+
directory = dirname(directory);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return undefined;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const readPackageImports = (importer: string): PackageImports | undefined => {
|
|
287
|
+
const file = nearestPackageFile(importer);
|
|
288
|
+
|
|
289
|
+
if (file === undefined) {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const manifest = parseJSON(readFileSync(file, "utf8"));
|
|
295
|
+
|
|
296
|
+
return isRecord(manifest) && isRecord(manifest.imports)
|
|
297
|
+
? { file, imports: manifest.imports, root: dirname(file) }
|
|
298
|
+
: undefined;
|
|
299
|
+
} catch {
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
function stringTargets(value: unknown): string[] {
|
|
305
|
+
if (typeof value === "string") {
|
|
306
|
+
return [value];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (Array.isArray(value)) {
|
|
310
|
+
return value.flatMap((item) => stringTargets(item));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return isRecord(value)
|
|
314
|
+
? Object.values(value).flatMap((item) => stringTargets(item))
|
|
315
|
+
: [];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const importPatternMatch = (pattern: string, source: string): string | undefined => {
|
|
319
|
+
if (pattern === source) {
|
|
320
|
+
return "";
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const wildcard = pattern.indexOf("*");
|
|
324
|
+
|
|
325
|
+
if (wildcard === -1) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const prefix = pattern.slice(0, wildcard);
|
|
330
|
+
const suffix = pattern.slice(wildcard + 1);
|
|
331
|
+
|
|
332
|
+
return source.startsWith(prefix) && source.endsWith(suffix)
|
|
333
|
+
? source.slice(prefix.length, source.length - suffix.length)
|
|
334
|
+
: undefined;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const packageImportTargets = (source: string, imports: Record<string, unknown>): string[] =>
|
|
338
|
+
Object.entries(imports).flatMap(([pattern, value]) => {
|
|
339
|
+
const match = importPatternMatch(pattern, source);
|
|
340
|
+
|
|
341
|
+
return match === undefined
|
|
342
|
+
? []
|
|
343
|
+
: stringTargets(value).map((target) => target.split("*").join(match));
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const packageImportDependencies = (source: string, importer: string, configName: string): string[] => {
|
|
347
|
+
if (!source.startsWith("#")) {
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const packageImports = readPackageImports(importer);
|
|
352
|
+
|
|
353
|
+
if (packageImports === undefined) {
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const targets = packageImportTargets(source, packageImports.imports)
|
|
358
|
+
.filter((target) => target.startsWith("."))
|
|
359
|
+
.flatMap((target) => targetCandidates(resolve(packageImports.root, target), configName));
|
|
360
|
+
|
|
361
|
+
return [packageImports.file, ...targets];
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const normalizedResolvedPath = (path: string): string =>
|
|
365
|
+
path.startsWith("file:") ? fileURLToPath(path) : path;
|
|
366
|
+
|
|
367
|
+
const resolvedSourcePath = (source: string, importer: string, jiti: Jiti): string | undefined => {
|
|
368
|
+
try {
|
|
369
|
+
const path = jiti.esmResolve(source, {
|
|
370
|
+
parentURL: pathToFileURL(importer).href,
|
|
371
|
+
try: true,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
return path === undefined ? undefined : normalizedResolvedPath(path);
|
|
375
|
+
} catch {
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const sourceDependencies = (
|
|
381
|
+
source: string,
|
|
382
|
+
importer: string,
|
|
383
|
+
configName: string,
|
|
384
|
+
jiti: Jiti,
|
|
385
|
+
): string[] => {
|
|
386
|
+
const dependency = resolvedSourcePath(source, importer, jiti);
|
|
387
|
+
const packageDependencies = packageImportDependencies(source, importer, configName);
|
|
388
|
+
|
|
389
|
+
return dependency === undefined
|
|
390
|
+
? [...unresolvedCandidates(source, importer, configName), ...packageDependencies]
|
|
391
|
+
: [dependency, ...packageDependencies];
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const configLayerDirectory = (path: string, configName: string, configRoot: string): string => {
|
|
395
|
+
const segments = configName.split(/[\\/]/u).filter((segment) => segment.length > 0);
|
|
396
|
+
let directory = resolve(path);
|
|
397
|
+
let remaining = segments.length;
|
|
398
|
+
|
|
399
|
+
while (remaining > 0) {
|
|
400
|
+
directory = dirname(directory);
|
|
401
|
+
remaining -= 1;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return resolve(directory, ...segments) === resolve(path) ? directory : resolve(configRoot);
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const c12SourceDependencies = (
|
|
408
|
+
source: string,
|
|
409
|
+
importer: string,
|
|
410
|
+
configName: string,
|
|
411
|
+
configRoot: string,
|
|
412
|
+
): string[] => {
|
|
413
|
+
const clean = source.split(/[?#]/u, 1)[0] ?? "";
|
|
414
|
+
|
|
415
|
+
const cwd = configLayerDirectory(importer, configName, configRoot);
|
|
416
|
+
const target = localConfigSourcePath(clean, { cwd, configFile: configName });
|
|
417
|
+
|
|
418
|
+
return target === undefined ? [] : targetCandidates(target, configName);
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const directDependencies = (path: string, configName: string, configRoot: string): string[] => {
|
|
422
|
+
const module = MODULE_EXTENSIONS.has(extname(path).toLowerCase()) ? moduleSources(path) : undefined;
|
|
423
|
+
const sources = module?.all ?? dataSources(path);
|
|
424
|
+
const extended = module?.extended ?? sources;
|
|
425
|
+
const jiti = createJiti(path, {
|
|
426
|
+
extensions: RESOLVABLE_EXTENSIONS,
|
|
427
|
+
fsCache: false,
|
|
428
|
+
moduleCache: false,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return [
|
|
432
|
+
...sources.flatMap((source) => sourceDependencies(source, path, configName, jiti)),
|
|
433
|
+
...extended.flatMap((source) => c12SourceDependencies(source, path, configName, configRoot)),
|
|
434
|
+
]
|
|
435
|
+
.filter((dependency) => !dependency.includes(`${sep}node_modules${sep}`));
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const resolveConfigDependencies = (
|
|
439
|
+
configFile: string,
|
|
440
|
+
configName = basename(configFile),
|
|
441
|
+
configRoot = dirname(configFile),
|
|
442
|
+
): string[] => {
|
|
443
|
+
const dependencies: Set<string> = new Set();
|
|
444
|
+
const pending = [resolve(configFile)];
|
|
445
|
+
|
|
446
|
+
while (pending.length > 0) {
|
|
447
|
+
const path = pending.pop();
|
|
448
|
+
|
|
449
|
+
if (path === undefined) {
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (dependencies.has(path)) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
dependencies.add(path);
|
|
458
|
+
pending.push(...directDependencies(path, configName, configRoot));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return [...dependencies];
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
export { resolveConfigDependencies };
|