@ts-for-gir/cli 4.0.0-beta.9 → 4.0.0-rc.10
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 +335 -86
- package/bin/ts-for-gir +28832 -0
- package/bin/ts-for-gir-dev +43 -0
- package/bin/ts-for-gir-gjs +348955 -0
- package/dist-templates/types-locally/.ts-for-girrc.js +6 -0
- package/dist-templates/types-locally/README.md +15 -0
- package/dist-templates/types-locally/esbuild.ts +10 -0
- package/dist-templates/types-locally/main.ts +21 -0
- package/dist-templates/types-locally/package.json +18 -0
- package/dist-templates/types-locally/tsconfig.json +17 -0
- package/dist-templates/types-npm/README.md +14 -0
- package/dist-templates/types-npm/esbuild.ts +10 -0
- package/dist-templates/types-npm/main.ts +19 -0
- package/dist-templates/types-npm/package.json +23 -0
- package/dist-templates/types-npm/tsconfig.json +15 -0
- package/dist-templates/types-workspace/.ts-for-girrc.js +12 -0
- package/dist-templates/types-workspace/README.md +26 -0
- package/dist-templates/types-workspace/package.json +22 -0
- package/dist-templates/types-workspace/packages/app/esbuild.ts +10 -0
- package/dist-templates/types-workspace/packages/app/main.ts +19 -0
- package/dist-templates/types-workspace/packages/app/package.json +23 -0
- package/dist-templates/types-workspace/packages/app/tsconfig.json +15 -0
- package/dist-templates/types-workspace/tsconfig.json +11 -0
- package/package.json +60 -37
- package/src/commands/analyze.ts +344 -0
- package/src/commands/command-builder.ts +30 -0
- package/src/commands/copy.ts +71 -76
- package/src/commands/create.ts +223 -0
- package/src/commands/doc.ts +44 -47
- package/src/commands/generate.ts +35 -79
- package/src/commands/index.ts +8 -4
- package/src/commands/json.ts +43 -0
- package/src/commands/list.ts +71 -90
- package/src/commands/run-generation-command.ts +75 -0
- package/src/commands/self-update.ts +142 -0
- package/src/config/config-loader.ts +238 -0
- package/src/config/config-writer.ts +52 -0
- package/src/config/defaults.ts +68 -0
- package/src/config/index.ts +16 -0
- package/src/config/options.ts +365 -0
- package/src/config.ts +3 -456
- package/src/formatters/typescript-formatter.ts +17 -0
- package/src/generation-handler.ts +122 -67
- package/src/index.ts +4 -4
- package/src/module-loader/dependency-resolver.ts +100 -0
- package/src/module-loader/file-finder.ts +65 -0
- package/src/module-loader/index.ts +8 -0
- package/src/module-loader/module-grouper.ts +77 -0
- package/src/module-loader/prompt-handler.ts +111 -0
- package/src/module-loader.ts +321 -578
- package/src/start.ts +36 -15
- package/src/types/command-args.ts +154 -0
- package/src/types/command-definition.ts +17 -0
- package/src/types/commands.ts +30 -0
- package/src/types/index.ts +15 -0
- package/src/types/report-types.ts +34 -0
- package/lib/commands/copy.d.ts +0 -12
- package/lib/commands/copy.js +0 -80
- package/lib/commands/copy.js.map +0 -1
- package/lib/commands/doc.d.ts +0 -12
- package/lib/commands/doc.js +0 -40
- package/lib/commands/doc.js.map +0 -1
- package/lib/commands/generate.d.ts +0 -12
- package/lib/commands/generate.js +0 -71
- package/lib/commands/generate.js.map +0 -1
- package/lib/commands/index.d.ts +0 -4
- package/lib/commands/index.js +0 -5
- package/lib/commands/index.js.map +0 -1
- package/lib/commands/list.d.ts +0 -12
- package/lib/commands/list.js +0 -81
- package/lib/commands/list.js.map +0 -1
- package/lib/config.d.ts +0 -108
- package/lib/config.js +0 -410
- package/lib/config.js.map +0 -1
- package/lib/generation-handler.d.ts +0 -10
- package/lib/generation-handler.js +0 -48
- package/lib/generation-handler.js.map +0 -1
- package/lib/index.d.ts +0 -4
- package/lib/index.js +0 -5
- package/lib/index.js.map +0 -1
- package/lib/module-loader.d.ts +0 -148
- package/lib/module-loader.js +0 -468
- package/lib/module-loader.js.map +0 -1
- package/lib/start.d.ts +0 -2
- package/lib/start.js +0 -16
- package/lib/start.js.map +0 -1
package/src/commands/list.ts
CHANGED
|
@@ -2,101 +2,82 @@
|
|
|
2
2
|
* Everything you need for the `ts-for-gir list` command is located here
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import type { ConfigFlags } from "@ts-for-gir/lib";
|
|
6
|
+
import { APP_NAME, ERROR_NO_MODULES_FOUND, Logger, NSRegistry } from "@ts-for-gir/lib";
|
|
7
|
+
import { getOptionsGeneration, listOptions, load } from "../config.ts";
|
|
8
|
+
import { ModuleLoader } from "../module-loader.ts";
|
|
9
|
+
import type { ListCommandArgs } from "../types/index.ts";
|
|
10
|
+
import { createBuilder } from "./command-builder.ts";
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
const command = "list [modules..]";
|
|
11
13
|
|
|
12
|
-
const
|
|
14
|
+
const description = "Lists all available GIR modules";
|
|
13
15
|
|
|
14
|
-
const
|
|
16
|
+
const logger = new Logger(true, "ListCommand");
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
-
const handler = async (args: ConfigFlags) => {
|
|
27
|
-
const config = await Config.load(args)
|
|
28
|
-
const generateConfig = Config.getOptionsGeneration(config)
|
|
29
|
-
const moduleLoader = new ModuleLoader(generateConfig)
|
|
30
|
-
const { grouped, failed } = await moduleLoader.getModules(config.modules, config.ignore)
|
|
31
|
-
const moduleGroups = Object.values(grouped)
|
|
32
|
-
if (Object.keys(grouped).length === 0) {
|
|
33
|
-
return Logger.error(ERROR_NO_MODULES_FOUND(config.girDirectories))
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const conflictModules = moduleGroups.filter((moduleGroup) => moduleGroup.hasConflict)
|
|
37
|
-
|
|
38
|
-
const byHandModules = moduleGroups.filter(
|
|
39
|
-
(moduleGroup) => moduleGroup.modules[0].resolvedBy === ResolveType.BY_HAND,
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
const depModules = moduleGroups.filter(
|
|
43
|
-
(moduleGroup) => moduleGroup.modules[0].resolvedBy === ResolveType.DEPENDENCE,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
Logger.info('\nSearch for gir files in:')
|
|
47
|
-
for (const dir of config.girDirectories) {
|
|
48
|
-
Logger.white(`- ${dir}`)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
Logger.info('\nSelected Modules:')
|
|
52
|
-
for (const moduleGroup of byHandModules) {
|
|
53
|
-
for (const depModule of moduleGroup.modules) {
|
|
54
|
-
Logger.white(`- ${depModule.packageName}`)
|
|
55
|
-
Logger.gray(` - ${depModule.path}`)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (depModules.length > 0) {
|
|
60
|
-
Logger.yellow('\nDependencies:')
|
|
61
|
-
for (const moduleGroup of depModules) {
|
|
62
|
-
for (const depModule of moduleGroup.modules) {
|
|
63
|
-
Logger.white(`- ${depModule.packageName}`)
|
|
64
|
-
Logger.gray(`- ${depModule.path}`)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (conflictModules.length > 0) {
|
|
70
|
-
Logger.danger('\nConflicts:')
|
|
71
|
-
for (const moduleGroup of conflictModules) {
|
|
72
|
-
Logger.white(`- ${moduleGroup.namespace}`)
|
|
73
|
-
for (const conflictModule of moduleGroup.modules) {
|
|
74
|
-
Logger.white(` - ${conflictModule.packageName}`)
|
|
75
|
-
Logger.gray(` - ${conflictModule.path}`)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
18
|
+
const examples: ReadonlyArray<[string, string?]> = [
|
|
19
|
+
[`${APP_NAME} list -g ./vala-girs/gir-1.0`, "Lists all available GIR modules in ./vala-girs/gir-1.0"],
|
|
20
|
+
[
|
|
21
|
+
`${APP_NAME} list --ignore=Gtk-3.0 xrandr-1.3`,
|
|
22
|
+
"Lists all available GIR modules in /usr/share/gir-1.0 but not Gtk-3.0 and xrandr-1.3",
|
|
23
|
+
],
|
|
24
|
+
];
|
|
79
25
|
|
|
80
|
-
|
|
81
|
-
Logger.danger('\nDependencies not found:')
|
|
82
|
-
for (const fail of failed) {
|
|
83
|
-
Logger.white(`- ${fail}`)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
26
|
+
const builder = createBuilder<ListCommandArgs>(listOptions, examples);
|
|
87
27
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
28
|
+
const handler = async (args: ConfigFlags) => {
|
|
29
|
+
const config = await load(args);
|
|
30
|
+
const generateConfig = getOptionsGeneration(config);
|
|
31
|
+
const registry = new NSRegistry();
|
|
32
|
+
const moduleLoader = new ModuleLoader(generateConfig, registry);
|
|
33
|
+
const { grouped, failed } = await moduleLoader.getModules(config.modules, config.ignore);
|
|
34
|
+
const moduleGroups = Object.values(grouped);
|
|
35
|
+
|
|
36
|
+
if (Object.keys(grouped).length === 0) {
|
|
37
|
+
return logger.error(ERROR_NO_MODULES_FOUND(config.girDirectories));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const conflictModules = moduleGroups.filter((moduleGroup) => moduleGroup.hasConflict);
|
|
41
|
+
const allModules = moduleGroups.filter((moduleGroup) => !moduleGroup.hasConflict);
|
|
42
|
+
|
|
43
|
+
logger.info("\nSearch for gir files in:");
|
|
44
|
+
for (const dir of config.girDirectories) {
|
|
45
|
+
logger.white(`- ${dir}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Show all available modules
|
|
49
|
+
logger.info("\nAvailable Modules:");
|
|
50
|
+
for (const moduleGroup of allModules) {
|
|
51
|
+
for (const module of moduleGroup.modules) {
|
|
52
|
+
logger.white(`- ${module.packageName}`);
|
|
53
|
+
logger.gray(` - ${module.path}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Only show sections if there is actual content
|
|
58
|
+
if (conflictModules.length > 0) {
|
|
59
|
+
logger.danger("\nConflicts:");
|
|
60
|
+
for (const moduleGroup of conflictModules) {
|
|
61
|
+
logger.white(`- ${moduleGroup.namespace}`);
|
|
62
|
+
for (const conflictModule of moduleGroup.modules) {
|
|
63
|
+
logger.white(` - ${conflictModule.packageName}`);
|
|
64
|
+
logger.gray(` - ${conflictModule.path}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (failed.length > 0) {
|
|
70
|
+
logger.danger("\nDependencies not found:");
|
|
71
|
+
for (const fail of failed) {
|
|
72
|
+
logger.white(`- ${fail}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
95
76
|
|
|
96
77
|
export const list = {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
78
|
+
command,
|
|
79
|
+
description,
|
|
80
|
+
builder,
|
|
81
|
+
handler,
|
|
82
|
+
examples,
|
|
83
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { GeneratorType } from "@ts-for-gir/generator-base";
|
|
2
|
+
import {
|
|
3
|
+
type ConfigFlags,
|
|
4
|
+
ERROR_NO_MODULES_FOUND,
|
|
5
|
+
type GirModule,
|
|
6
|
+
Logger,
|
|
7
|
+
NSRegistry,
|
|
8
|
+
ReporterService,
|
|
9
|
+
ResolveType,
|
|
10
|
+
} from "@ts-for-gir/lib";
|
|
11
|
+
import { getOptionsGeneration, load } from "../config.ts";
|
|
12
|
+
import { GenerationHandler } from "../generation-handler.ts";
|
|
13
|
+
import { ModuleLoader } from "../module-loader.ts";
|
|
14
|
+
|
|
15
|
+
interface GenerationCommandOptions {
|
|
16
|
+
generatorType: GeneratorType;
|
|
17
|
+
loggerName: string;
|
|
18
|
+
configureRegistry?: (registry: NSRegistry) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function runGenerationCommand(args: ConfigFlags, options: GenerationCommandOptions): Promise<void> {
|
|
22
|
+
const config = await load(args);
|
|
23
|
+
const generateConfig = getOptionsGeneration(config);
|
|
24
|
+
const logger = new Logger(true, options.loggerName);
|
|
25
|
+
const registry = new NSRegistry();
|
|
26
|
+
|
|
27
|
+
options.configureRegistry?.(registry);
|
|
28
|
+
|
|
29
|
+
const moduleLoader = new ModuleLoader(generateConfig, registry);
|
|
30
|
+
|
|
31
|
+
let tsForGir: GenerationHandler | null = null;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const { keep } = await moduleLoader.getModulesResolved(
|
|
35
|
+
config.modules,
|
|
36
|
+
config.ignore || [],
|
|
37
|
+
config.ignoreVersionConflicts,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (keep.length === 0) {
|
|
41
|
+
logger.error(ERROR_NO_MODULES_FOUND(config.girDirectories));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
tsForGir = new GenerationHandler(generateConfig, options.generatorType, registry);
|
|
46
|
+
|
|
47
|
+
moduleLoader.parse(keep);
|
|
48
|
+
|
|
49
|
+
// In external-deps mode, only generate the user-requested module(s). Transitively-loaded
|
|
50
|
+
// deps stay in the registry for type resolution but must not produce their own output.
|
|
51
|
+
const toGenerate = generateConfig.externalDeps ? keep.filter((m) => m.resolvedBy === ResolveType.BY_HAND) : keep;
|
|
52
|
+
|
|
53
|
+
const girModules = Array.from(toGenerate).map((girModuleResolvedBy) => girModuleResolvedBy.module as GirModule);
|
|
54
|
+
|
|
55
|
+
await tsForGir.start(girModules);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (generateConfig.reporter && tsForGir) {
|
|
58
|
+
const service = ReporterService.getInstance();
|
|
59
|
+
|
|
60
|
+
if (tsForGir.log) {
|
|
61
|
+
tsForGir.log.reportGenerationFailure(
|
|
62
|
+
"Main",
|
|
63
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
64
|
+
"Generation failed",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const report = service.generateComprehensiveReport();
|
|
69
|
+
service.printComprehensiveSummary(report);
|
|
70
|
+
await service.saveComprehensiveReport(undefined, report);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything you need for the `ts-for-gir self-update` command is located here
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { chmodSync, existsSync, renameSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
import { APP_NAME, APP_VERSION } from "@ts-for-gir/lib";
|
|
10
|
+
|
|
11
|
+
import type { SelfUpdateCommandArgs } from "../types/index.ts";
|
|
12
|
+
|
|
13
|
+
const REPO = "gjsify/ts-for-gir";
|
|
14
|
+
const GITHUB_API = "https://api.github.com";
|
|
15
|
+
const GJS_ASSET_NAME = "ts-for-gir-gjs";
|
|
16
|
+
|
|
17
|
+
function getCurrentBinaryPath(): string | null {
|
|
18
|
+
const p = process.argv[1] ?? null;
|
|
19
|
+
if (!p) return null;
|
|
20
|
+
// Refuse to update in dev mode (source file or node_modules path)
|
|
21
|
+
if (p.endsWith(".ts") || p.includes("node_modules")) return null;
|
|
22
|
+
return p;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function fetchJson(url: string): Promise<unknown> {
|
|
26
|
+
const headers: Record<string, string> = {
|
|
27
|
+
Accept: "application/vnd.github.v3+json",
|
|
28
|
+
"User-Agent": `ts-for-gir/${APP_VERSION}`,
|
|
29
|
+
};
|
|
30
|
+
const token = process.env.GITHUB_TOKEN;
|
|
31
|
+
if (token) headers.Authorization = `token ${token}`;
|
|
32
|
+
|
|
33
|
+
const response = await fetch(url, { headers });
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`HTTP ${response.status} from ${url}`);
|
|
36
|
+
}
|
|
37
|
+
return response.json();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function downloadBinary(url: string, destPath: string): Promise<void> {
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
headers: { "User-Agent": `ts-for-gir/${APP_VERSION}` },
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error(`HTTP ${response.status} downloading binary`);
|
|
46
|
+
}
|
|
47
|
+
const buffer = await response.arrayBuffer();
|
|
48
|
+
const tmpPath = join(tmpdir(), `ts-for-gir-update-${Date.now()}`);
|
|
49
|
+
writeFileSync(tmpPath, Buffer.from(buffer));
|
|
50
|
+
chmodSync(tmpPath, 0o755);
|
|
51
|
+
renameSync(tmpPath, destPath); // atomic on POSIX
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const command = "self-update";
|
|
55
|
+
const description = "Update ts-for-gir to the latest version from GitHub releases";
|
|
56
|
+
const examples: ReadonlyArray<[string, string?]> = [
|
|
57
|
+
[`${APP_NAME} self-update`, "Check for updates and install the latest version"],
|
|
58
|
+
[`${APP_NAME} self-update --check`, "Only check for a newer version, do not install"],
|
|
59
|
+
[`${APP_NAME} self-update --force`, "Force reinstall even if already on the latest version"],
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const builder = (yargs: import("yargs").Argv) =>
|
|
63
|
+
yargs
|
|
64
|
+
.option("check", {
|
|
65
|
+
description: "Only check for a newer version, do not install",
|
|
66
|
+
type: "boolean",
|
|
67
|
+
default: false,
|
|
68
|
+
})
|
|
69
|
+
.option("force", {
|
|
70
|
+
description: "Force reinstall even if already on the latest version",
|
|
71
|
+
type: "boolean",
|
|
72
|
+
default: false,
|
|
73
|
+
})
|
|
74
|
+
.example(examples as [string, string?][]);
|
|
75
|
+
|
|
76
|
+
const handler = async (args: SelfUpdateCommandArgs): Promise<void> => {
|
|
77
|
+
console.log(`Checking for updates... (current: v${APP_VERSION})`);
|
|
78
|
+
|
|
79
|
+
let release: Record<string, unknown>;
|
|
80
|
+
try {
|
|
81
|
+
release = (await fetchJson(`${GITHUB_API}/repos/${REPO}/releases/latest`)) as Record<string, unknown>;
|
|
82
|
+
} catch (err) {
|
|
83
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
84
|
+
process.stderr.write(`Failed to fetch release information: ${msg}\n`);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const latestVersion = (release.tag_name as string).replace(/^v/, "");
|
|
90
|
+
|
|
91
|
+
if (latestVersion === APP_VERSION && !args.force) {
|
|
92
|
+
console.log(`Already up to date (v${APP_VERSION})`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (args.check) {
|
|
97
|
+
console.log(`New version available: v${latestVersion} (current: v${APP_VERSION})`);
|
|
98
|
+
console.log(`Run \`${APP_NAME} self-update\` to install it.`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`Updating to v${latestVersion}...`);
|
|
103
|
+
|
|
104
|
+
const assets = release.assets as Array<Record<string, string>>;
|
|
105
|
+
const asset = assets.find((a) => a.name === GJS_ASSET_NAME);
|
|
106
|
+
if (!asset) {
|
|
107
|
+
process.stderr.write(
|
|
108
|
+
`No GJS binary found in release ${release.tag_name}.\n` +
|
|
109
|
+
"self-update requires the GJS bundle to be installed via install.js.\n" +
|
|
110
|
+
"For npm installations use: npm update -g @ts-for-gir/cli\n",
|
|
111
|
+
);
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const currentPath = getCurrentBinaryPath();
|
|
117
|
+
if (!currentPath || !existsSync(currentPath)) {
|
|
118
|
+
process.stderr.write(
|
|
119
|
+
"Cannot determine current binary path for update.\n" +
|
|
120
|
+
"self-update only works when running the installed GJS binary.\n",
|
|
121
|
+
);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await downloadBinary(asset.browser_download_url, currentPath);
|
|
128
|
+
console.log(`Successfully updated to v${latestVersion}`);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
131
|
+
process.stderr.write(`Update failed: ${msg}\n`);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const selfUpdate = {
|
|
137
|
+
command,
|
|
138
|
+
description,
|
|
139
|
+
builder,
|
|
140
|
+
handler,
|
|
141
|
+
examples,
|
|
142
|
+
};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config loader functionality for ts-for-gir
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import type { ConfigFlags, OptionsGeneration, UserConfig, UserConfigLoadResult } from "@ts-for-gir/lib";
|
|
7
|
+
import { APP_NAME, isEqual } from "@ts-for-gir/lib";
|
|
8
|
+
import { type Options as ConfigSearchOptions, cosmiconfig } from "cosmiconfig";
|
|
9
|
+
import { setConfigFilePath } from "./config-writer.ts";
|
|
10
|
+
import { defaults } from "./defaults.ts";
|
|
11
|
+
import { docOptions, options } from "./options.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The user can create a `.ts-for-girrc` file for his default configs,
|
|
15
|
+
* this method load this config file an returns the user configuration
|
|
16
|
+
* @param configName If the user uses a custom config file name
|
|
17
|
+
*/
|
|
18
|
+
export async function loadConfigFile(configName?: string): Promise<UserConfigLoadResult | null> {
|
|
19
|
+
const configSearchOptions: Partial<ConfigSearchOptions> = {
|
|
20
|
+
loaders: {
|
|
21
|
+
// ESM loader
|
|
22
|
+
".js": async (filepath) => {
|
|
23
|
+
const file = await import(filepath);
|
|
24
|
+
|
|
25
|
+
// Files with `exports.default = { ... }`
|
|
26
|
+
if (file?.default?.default) {
|
|
27
|
+
return file.default.default as Partial<UserConfig>;
|
|
28
|
+
}
|
|
29
|
+
// Files with `export default { ... }`
|
|
30
|
+
if (file?.default) {
|
|
31
|
+
return file.default as Partial<UserConfig>;
|
|
32
|
+
}
|
|
33
|
+
// Files with `export { ... }`
|
|
34
|
+
return file as Partial<UserConfig>;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (configName) {
|
|
40
|
+
configSearchOptions.searchPlaces = [configName];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const configFile: UserConfigLoadResult | null = await cosmiconfig(APP_NAME, configSearchOptions).search();
|
|
44
|
+
|
|
45
|
+
if (configFile?.filepath) {
|
|
46
|
+
setConfigFilePath(configFile.filepath);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return configFile;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Convert UserConfig to OptionsGeneration
|
|
54
|
+
*/
|
|
55
|
+
export function getOptionsGeneration(config: UserConfig): OptionsGeneration {
|
|
56
|
+
const generateConfig: OptionsGeneration = {
|
|
57
|
+
...config,
|
|
58
|
+
};
|
|
59
|
+
return generateConfig;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parse `Namespace=npm-package` strings (from repeatable `--external-package` flag) into a
|
|
64
|
+
* map. Silently drops entries that don't contain `=`. Empty input returns undefined so the
|
|
65
|
+
* field stays absent in the merged config (rather than `{}`, which would shadow rc values).
|
|
66
|
+
*/
|
|
67
|
+
function parseExternalPackagePairs(pairs: string[] | undefined): Record<string, string> | undefined {
|
|
68
|
+
if (!pairs || pairs.length === 0) return undefined;
|
|
69
|
+
const map: Record<string, string> = {};
|
|
70
|
+
for (const pair of pairs) {
|
|
71
|
+
const eq = pair.indexOf("=");
|
|
72
|
+
if (eq < 1) continue;
|
|
73
|
+
const ns = pair.slice(0, eq).trim();
|
|
74
|
+
const pkg = pair.slice(eq + 1).trim();
|
|
75
|
+
if (ns && pkg) map[ns] = pkg;
|
|
76
|
+
}
|
|
77
|
+
return Object.keys(map).length > 0 ? map : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validate the configuration
|
|
82
|
+
*/
|
|
83
|
+
export function validate(config: UserConfig): UserConfig {
|
|
84
|
+
return config;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Merge a single config value from file config to user config
|
|
89
|
+
* @param userConfig The user config object to update
|
|
90
|
+
* @param configFileData The config file data to merge from
|
|
91
|
+
* @param key The config key to merge
|
|
92
|
+
* @param optionDefault The default value from options
|
|
93
|
+
* @param validator Optional validation function
|
|
94
|
+
*/
|
|
95
|
+
const isBoolean = (v: unknown) => typeof v === "boolean";
|
|
96
|
+
|
|
97
|
+
function mergeConfigValue<K extends keyof UserConfig>(
|
|
98
|
+
userConfig: UserConfig,
|
|
99
|
+
configFileData: Partial<UserConfig>,
|
|
100
|
+
key: K,
|
|
101
|
+
optionDefault: unknown,
|
|
102
|
+
validator?: (value: unknown) => boolean,
|
|
103
|
+
): void {
|
|
104
|
+
const fileValue = configFileData[key];
|
|
105
|
+
const userValue = userConfig[key];
|
|
106
|
+
|
|
107
|
+
// Skip if no file value
|
|
108
|
+
if (fileValue === undefined) return;
|
|
109
|
+
|
|
110
|
+
// Apply validator if provided
|
|
111
|
+
if (validator && !validator(fileValue)) return;
|
|
112
|
+
|
|
113
|
+
// Check if user value is default
|
|
114
|
+
const isDefault =
|
|
115
|
+
userValue === optionDefault ||
|
|
116
|
+
(Array.isArray(userValue) && Array.isArray(optionDefault) && isEqual(userValue, optionDefault));
|
|
117
|
+
|
|
118
|
+
if (isDefault) {
|
|
119
|
+
(userConfig[key] as UserConfig[K]) = fileValue as UserConfig[K];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Loads the values of the config file and concatenate them with passed cli flags / arguments.
|
|
125
|
+
* The values from config file are preferred if the cli flag value is the default (and so not set / overwritten)
|
|
126
|
+
* @param cliOptions CLI options passed by the user
|
|
127
|
+
*/
|
|
128
|
+
export async function load(cliOptions: ConfigFlags): Promise<UserConfig> {
|
|
129
|
+
const configFile = await loadConfigFile(cliOptions.configName);
|
|
130
|
+
const configFileData = configFile?.config || {};
|
|
131
|
+
|
|
132
|
+
// `--external-package GLib=@girs/glib-2.0` arrives as a string[]; collapse to Record.
|
|
133
|
+
// Drop the raw array so it doesn't pollute the merged UserConfig surface.
|
|
134
|
+
const externalPackagesFromCli = parseExternalPackagePairs(
|
|
135
|
+
(cliOptions as { externalPackage?: string[] }).externalPackage,
|
|
136
|
+
);
|
|
137
|
+
const { externalPackage: _externalPackage, ...cliOptionsClean } = cliOptions as ConfigFlags & {
|
|
138
|
+
externalPackage?: string[];
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const userConfig: UserConfig = {
|
|
142
|
+
...cliOptionsClean,
|
|
143
|
+
};
|
|
144
|
+
if (externalPackagesFromCli) {
|
|
145
|
+
userConfig.externalPackages = externalPackagesFromCli;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (configFileData) {
|
|
149
|
+
// Boolean options — config file overrides CLI defaults
|
|
150
|
+
const booleanKeys: Array<[keyof UserConfig, unknown]> = [
|
|
151
|
+
["verbose", options.verbose.default],
|
|
152
|
+
["ignoreVersionConflicts", options.ignoreVersionConflicts.default],
|
|
153
|
+
["print", options.print.default],
|
|
154
|
+
["noNamespace", options.noNamespace.default],
|
|
155
|
+
["noComments", options.noComments.default],
|
|
156
|
+
["promisify", options.promisify.default],
|
|
157
|
+
["workspace", options.workspace.default],
|
|
158
|
+
["onlyVersionPrefix", options.onlyVersionPrefix.default],
|
|
159
|
+
["noPrettyPrint", options.noPrettyPrint.default],
|
|
160
|
+
["noAdvancedVariants", options.noAdvancedVariants.default],
|
|
161
|
+
["package", options.package.default],
|
|
162
|
+
["reporter", options.reporter.default],
|
|
163
|
+
["externalDeps", options.externalDeps.default],
|
|
164
|
+
["allowMissingDeps", options.allowMissingDeps.default],
|
|
165
|
+
["combined", docOptions.combined.default],
|
|
166
|
+
["merge", docOptions.merge.default],
|
|
167
|
+
];
|
|
168
|
+
for (const [key, defaultVal] of booleanKeys) {
|
|
169
|
+
mergeConfigValue(userConfig, configFileData, key, defaultVal, isBoolean);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// String options — config file overrides CLI defaults
|
|
173
|
+
const stringKeys: Array<[keyof UserConfig, unknown]> = [
|
|
174
|
+
["npmScope", options.npmScope.default],
|
|
175
|
+
["reporterOutput", options.reporterOutput.default],
|
|
176
|
+
["depVersionFormat", undefined],
|
|
177
|
+
["theme", docOptions.theme.default],
|
|
178
|
+
["sourceLinkTemplate", undefined],
|
|
179
|
+
["readme", undefined],
|
|
180
|
+
["jsonDir", undefined],
|
|
181
|
+
];
|
|
182
|
+
for (const [key, defaultVal] of stringKeys) {
|
|
183
|
+
mergeConfigValue(userConfig, configFileData, key, defaultVal);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Array options — config file overrides CLI defaults
|
|
187
|
+
const arrayKeys: Array<[keyof UserConfig, unknown]> = [
|
|
188
|
+
["ignore", options.ignore.default],
|
|
189
|
+
["modules", options.modules.default],
|
|
190
|
+
];
|
|
191
|
+
for (const [key, defaultVal] of arrayKeys) {
|
|
192
|
+
mergeConfigValue(userConfig, configFileData, key, defaultVal);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// girDirectories: rc-file entries are prepended to the current dirs (CLI-provided or
|
|
196
|
+
// system defaults) rather than replacing them. This lets projects add local GIR dirs
|
|
197
|
+
// (e.g. a Vala build output) without having to enumerate all system paths in the rc.
|
|
198
|
+
// To use ONLY the specified dirs (no system fallback), pass --girDirectories on the CLI.
|
|
199
|
+
if (configFileData.girDirectories?.length) {
|
|
200
|
+
const current = userConfig.girDirectories as string[];
|
|
201
|
+
const toAdd = (configFileData.girDirectories as string[]).filter((d) => !current.includes(d));
|
|
202
|
+
if (toAdd.length > 0) {
|
|
203
|
+
userConfig.girDirectories = [...toAdd, ...current];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Special handling for root
|
|
208
|
+
if (userConfig.root === options.root.default && (configFileData.root || configFile?.filepath)) {
|
|
209
|
+
userConfig.root =
|
|
210
|
+
configFileData.root || (configFile?.filepath ? dirname(configFile.filepath) : (options.root.default as string));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Special handling for outdir (override with config file value if still at a default)
|
|
214
|
+
const isDefaultOutdir = userConfig.outdir === options.outdir.default || userConfig.outdir === defaults.docOutdir;
|
|
215
|
+
if (isDefaultOutdir && configFileData.outdir) {
|
|
216
|
+
userConfig.outdir = userConfig.print ? null : configFileData.outdir;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// externalPackages is a Record<string, string> in rc files; CLI overrides take precedence.
|
|
220
|
+
if (!externalPackagesFromCli && configFileData.externalPackages) {
|
|
221
|
+
userConfig.externalPackages = configFileData.externalPackages;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Make paths absolute relative to root
|
|
226
|
+
const resolveToRoot = (path: string) => (path.startsWith("/") ? path : resolve(userConfig.root, path));
|
|
227
|
+
|
|
228
|
+
if (userConfig.outdir) {
|
|
229
|
+
userConfig.outdir = resolveToRoot(userConfig.outdir);
|
|
230
|
+
}
|
|
231
|
+
if (userConfig.jsonDir) {
|
|
232
|
+
userConfig.jsonDir = resolveToRoot(userConfig.jsonDir);
|
|
233
|
+
}
|
|
234
|
+
if (userConfig.girDirectories) {
|
|
235
|
+
userConfig.girDirectories = userConfig.girDirectories.map(resolveToRoot);
|
|
236
|
+
}
|
|
237
|
+
return validate(userConfig);
|
|
238
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config writer functionality for ts-for-gir
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { writeFile } from "node:fs/promises";
|
|
6
|
+
import { extname, join } from "node:path";
|
|
7
|
+
import type { UserConfig } from "@ts-for-gir/lib";
|
|
8
|
+
import { ERROR_CONFIG_EXTENSION_UNSUPPORTED, Logger, merge } from "@ts-for-gir/lib";
|
|
9
|
+
import { loadConfigFile } from "./config-loader.ts";
|
|
10
|
+
import { defaults } from "./defaults.ts";
|
|
11
|
+
|
|
12
|
+
const logger = new Logger(false, "ConfigWriter");
|
|
13
|
+
|
|
14
|
+
export let configFilePath = join(process.cwd(), defaults.configName);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Update the config file path when a config is loaded
|
|
18
|
+
*/
|
|
19
|
+
export function setConfigFilePath(path: string): void {
|
|
20
|
+
configFilePath = path;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Overwrites values in the user config file
|
|
25
|
+
* @param configsToAdd Configuration values to add/update
|
|
26
|
+
* @param configName Optional custom config file name
|
|
27
|
+
*/
|
|
28
|
+
export async function addToConfig(configsToAdd: Partial<UserConfig>, configName?: string): Promise<void> {
|
|
29
|
+
const userConfig = await loadConfigFile(configName);
|
|
30
|
+
const path = userConfig?.filepath || configFilePath;
|
|
31
|
+
const configToStore = {};
|
|
32
|
+
merge(configToStore, userConfig?.config || {}, configsToAdd);
|
|
33
|
+
|
|
34
|
+
const fileExtension = extname(path);
|
|
35
|
+
let writeConfigString = "";
|
|
36
|
+
|
|
37
|
+
switch (fileExtension) {
|
|
38
|
+
case ".js":
|
|
39
|
+
writeConfigString = `export default ${JSON.stringify(configToStore, null, 4)}`;
|
|
40
|
+
break;
|
|
41
|
+
case ".json":
|
|
42
|
+
writeConfigString = `${JSON.stringify(configToStore, null, 4)}`;
|
|
43
|
+
break;
|
|
44
|
+
default:
|
|
45
|
+
logger.error(ERROR_CONFIG_EXTENSION_UNSUPPORTED);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (writeConfigString && path) {
|
|
50
|
+
return writeFile(path, writeConfigString);
|
|
51
|
+
}
|
|
52
|
+
}
|