@triflare/fx 1.0.3 → 2.1.0-beta.1
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 +59 -0
- package/bin/fx.js +4 -1162
- package/package.json +8 -4
- package/product.yml +1 -1
- package/src/brand.js +9 -3
- package/src/catalog.js +103 -0
- package/src/cli/commands/build.js +98 -0
- package/src/cli/commands/catalog.js +42 -0
- package/src/cli/commands/check.js +44 -0
- package/src/cli/commands/completion.js +54 -0
- package/src/cli/commands/decompile.js +266 -0
- package/src/cli/commands/dev.js +179 -0
- package/src/cli/commands/extension.js +110 -0
- package/src/cli/commands/feature.js +114 -0
- package/src/cli/commands/fmt.js +173 -0
- package/src/cli/commands/index.js +45 -0
- package/src/cli/commands/init.js +169 -0
- package/src/cli/commands/lint.js +270 -0
- package/src/cli/commands/package.js +137 -0
- package/src/cli/commands/sprite/add.js +85 -0
- package/src/cli/commands/sprite/build.js +62 -0
- package/src/cli/commands/sprite/helpers.js +66 -0
- package/src/cli/commands/sprite/index.js +29 -0
- package/src/cli/commands/sprite/list.js +31 -0
- package/src/cli/commands/sprite/remove.js +51 -0
- package/src/cli/commands/vendor/add.js +98 -0
- package/src/cli/commands/vendor/helpers.js +50 -0
- package/src/cli/commands/vendor/index.js +39 -0
- package/src/cli/commands/vendor/install.js +64 -0
- package/src/cli/commands/vendor/list.js +45 -0
- package/src/cli/commands/vendor/migrate.js +47 -0
- package/src/cli/commands/vendor/outdated.js +51 -0
- package/src/cli/commands/vendor/remove.js +34 -0
- package/src/cli/commands/vendor/update.js +49 -0
- package/src/cli/commands/vendor/verify.js +44 -0
- package/src/cli/completion/bash.js +10 -0
- package/src/cli/completion/fish.js +9 -0
- package/src/cli/completion/index.js +97 -0
- package/src/cli/completion/zsh.js +11 -0
- package/src/cli/context.js +270 -0
- package/src/cli/define-command.js +147 -0
- package/src/cli/errors.js +111 -0
- package/src/cli/format.js +31 -0
- package/src/cli/prompts.js +183 -0
- package/src/cli/reporter.js +203 -0
- package/src/cli/run.js +248 -0
- package/src/cli/templates/index.js +112 -0
- package/src/cli/theme.js +84 -0
- package/src/compiler.js +133 -0
- package/src/config-edit.js +399 -1
- package/src/config.js +174 -4
- package/src/decompiler.js +45 -0
- package/src/extensions.js +72 -19
- package/src/features.js +339 -0
- package/src/formatter.js +499 -0
- package/src/index.js +31 -0
- package/src/lexer.js +45 -4
- package/src/lint/analyze.js +1414 -0
- package/src/lint/config-file.js +97 -0
- package/src/lint/config.js +174 -0
- package/src/lint/rules.js +461 -0
- package/src/linter.js +220 -0
- package/src/merge.js +109 -0
- package/src/parser.js +53 -13
- package/src/project.js +7 -87
- package/src/vendor.js +1323 -0
- package/vendor/pm-packager-dist/.fx-build.json +1 -1
- package/vendor/tempvars/fx-tempvars.js +321 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@triflare/fx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "2.1.0-beta.1",
|
|
4
4
|
"description": "Code PenguinMod projects with text.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
"exports": {
|
|
16
16
|
".": "./src/index.js",
|
|
17
17
|
"./catalog": "./src/catalog.js",
|
|
18
|
-
"./language": "./src/language.js"
|
|
18
|
+
"./language": "./src/language.js",
|
|
19
|
+
"./lint": "./src/lint/analyze.js"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"bin",
|
|
22
23
|
"src",
|
|
23
24
|
"vendor/pm-packager-dist",
|
|
25
|
+
"vendor/tempvars",
|
|
24
26
|
"product.yml",
|
|
25
27
|
"README.md"
|
|
26
28
|
],
|
|
@@ -40,12 +42,14 @@
|
|
|
40
42
|
"prepack": "node scripts/build-packager.js --if-missing"
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
|
-
"
|
|
44
|
-
"quickjs-emscripten": "^0.32.0",
|
|
45
|
+
"@clack/prompts": "^1.8.1",
|
|
45
46
|
"@fiahfy/icns": "0.0.7",
|
|
46
47
|
"@turbowarp/sbdl": "^7.0.0",
|
|
48
|
+
"commander": "^15.0.0",
|
|
47
49
|
"cross-fetch": "^4.1.0",
|
|
50
|
+
"js-yaml": "^5.4.3",
|
|
48
51
|
"jszip": "^3.10.1",
|
|
52
|
+
"quickjs-emscripten": "^0.32.0",
|
|
49
53
|
"sha.js": "^2.4.12"
|
|
50
54
|
},
|
|
51
55
|
"devDependencies": {
|
package/product.yml
CHANGED
package/src/brand.js
CHANGED
|
@@ -62,9 +62,10 @@ const HEX_COLOR = /^#(?:[\da-f]{3}|[\da-f]{6})$/i;
|
|
|
62
62
|
// styleText only learned hex colors in Node 24.16, and engines allow any 22, so
|
|
63
63
|
// the truecolor escape is built here. The gate is styleText's own: ask it to
|
|
64
64
|
// color a marker, and stay plain when it comes back untouched.
|
|
65
|
-
function colorize(text, color, stream) {
|
|
65
|
+
function colorize(text, color, stream, force = false) {
|
|
66
66
|
if (!HEX_COLOR.test(color)) return styleText(color, text, { stream });
|
|
67
|
-
if (!styleText("red", "\u0000", { stream }).includes("\u001b"))
|
|
67
|
+
if (!force && !styleText("red", "\u0000", { stream }).includes("\u001b"))
|
|
68
|
+
return text;
|
|
68
69
|
const digits =
|
|
69
70
|
color.length === 4 ? color.slice(1).replace(/./g, "$&$&") : color.slice(1);
|
|
70
71
|
const [red, green, blue] = digits
|
|
@@ -76,7 +77,11 @@ function colorize(text, color, stream) {
|
|
|
76
77
|
// Paints a product name or command in three equal bands taken straight from the
|
|
77
78
|
// logo palette. The colors disappear when the stream can't render them, so
|
|
78
79
|
// piped output and CI logs stay plain text.
|
|
79
|
-
export function brandText(
|
|
80
|
+
export function brandText(
|
|
81
|
+
text,
|
|
82
|
+
stream = process.stdout,
|
|
83
|
+
{ force = false } = {},
|
|
84
|
+
) {
|
|
80
85
|
const { primary, secondary, tertiary } = brandingColors();
|
|
81
86
|
const palette = [primary, secondary, tertiary];
|
|
82
87
|
const chars = Array.from(text);
|
|
@@ -96,6 +101,7 @@ export function brandText(text, stream = process.stdout) {
|
|
|
96
101
|
chars.slice(start, index).join(""),
|
|
97
102
|
palette[bandFor(start)],
|
|
98
103
|
stream,
|
|
104
|
+
force,
|
|
99
105
|
),
|
|
100
106
|
);
|
|
101
107
|
start = index;
|
package/src/catalog.js
CHANGED
|
@@ -610,6 +610,109 @@ const CORE_CATALOG = {
|
|
|
610
610
|
kind: "boolean",
|
|
611
611
|
fields: { VALUE: ["is penguinmod?", null] },
|
|
612
612
|
},
|
|
613
|
+
|
|
614
|
+
// ------------------------------------------------- temporary variables
|
|
615
|
+
// The Fx fork of Temporary Variables (`fxTempVars`). Entries are marked
|
|
616
|
+
// `requiresExtension` so the compiler can refuse to emit blocks whose
|
|
617
|
+
// extension is not loaded, and so the linter can point at the feature.
|
|
618
|
+
"tempvars.setThread": {
|
|
619
|
+
opcode: "fxTempVars_setThreadVariable",
|
|
620
|
+
extension: "fxTempVars",
|
|
621
|
+
requiresExtension: true,
|
|
622
|
+
inputs: {
|
|
623
|
+
VAR: { type: "string", default: "variable" },
|
|
624
|
+
STRING: { type: "string", default: "0" },
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
"tempvars.changeThread": {
|
|
628
|
+
opcode: "fxTempVars_changeThreadVariable",
|
|
629
|
+
extension: "fxTempVars",
|
|
630
|
+
requiresExtension: true,
|
|
631
|
+
inputs: {
|
|
632
|
+
VAR: { type: "string", default: "variable" },
|
|
633
|
+
NUM: { type: "number", default: "1" },
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
"tempvars.thread": {
|
|
637
|
+
opcode: "fxTempVars_getThreadVariable",
|
|
638
|
+
kind: "reporter",
|
|
639
|
+
extension: "fxTempVars",
|
|
640
|
+
requiresExtension: true,
|
|
641
|
+
inputs: { VAR: { type: "string", default: "variable" } },
|
|
642
|
+
},
|
|
643
|
+
"tempvars.threadExists": {
|
|
644
|
+
opcode: "fxTempVars_threadVariableExists",
|
|
645
|
+
kind: "boolean",
|
|
646
|
+
extension: "fxTempVars",
|
|
647
|
+
requiresExtension: true,
|
|
648
|
+
inputs: { VAR: { type: "string", default: "variable" } },
|
|
649
|
+
},
|
|
650
|
+
"tempvars.forEachThread": {
|
|
651
|
+
opcode: "fxTempVars_forEachThreadVariable",
|
|
652
|
+
extension: "fxTempVars",
|
|
653
|
+
requiresExtension: true,
|
|
654
|
+
inputs: {
|
|
655
|
+
VAR: { type: "string", default: "thread variable" },
|
|
656
|
+
NUM: { type: "number", default: "10" },
|
|
657
|
+
SUBSTACK: SUBSTACK,
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
"tempvars.threads": {
|
|
661
|
+
opcode: "fxTempVars_listThreadVariables",
|
|
662
|
+
kind: "reporter",
|
|
663
|
+
extension: "fxTempVars",
|
|
664
|
+
requiresExtension: true,
|
|
665
|
+
},
|
|
666
|
+
"tempvars.setRuntime": {
|
|
667
|
+
opcode: "fxTempVars_setRuntimeVariable",
|
|
668
|
+
extension: "fxTempVars",
|
|
669
|
+
requiresExtension: true,
|
|
670
|
+
inputs: {
|
|
671
|
+
VAR: { type: "string", default: "variable" },
|
|
672
|
+
STRING: { type: "string", default: "0" },
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
"tempvars.changeRuntime": {
|
|
676
|
+
opcode: "fxTempVars_changeRuntimeVariable",
|
|
677
|
+
extension: "fxTempVars",
|
|
678
|
+
requiresExtension: true,
|
|
679
|
+
inputs: {
|
|
680
|
+
VAR: { type: "string", default: "variable" },
|
|
681
|
+
// Upstream declares NUM as a string input; keep it so block JSON matches.
|
|
682
|
+
NUM: { type: "string", default: "1" },
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
"tempvars.runtime": {
|
|
686
|
+
opcode: "fxTempVars_getRuntimeVariable",
|
|
687
|
+
kind: "reporter",
|
|
688
|
+
extension: "fxTempVars",
|
|
689
|
+
requiresExtension: true,
|
|
690
|
+
inputs: { VAR: { type: "string", default: "variable" } },
|
|
691
|
+
},
|
|
692
|
+
"tempvars.runtimeExists": {
|
|
693
|
+
opcode: "fxTempVars_runtimeVariableExists",
|
|
694
|
+
kind: "boolean",
|
|
695
|
+
extension: "fxTempVars",
|
|
696
|
+
requiresExtension: true,
|
|
697
|
+
inputs: { VAR: { type: "string", default: "variable" } },
|
|
698
|
+
},
|
|
699
|
+
"tempvars.deleteRuntime": {
|
|
700
|
+
opcode: "fxTempVars_deleteRuntimeVariable",
|
|
701
|
+
extension: "fxTempVars",
|
|
702
|
+
requiresExtension: true,
|
|
703
|
+
inputs: { VAR: { type: "string", default: "variable" } },
|
|
704
|
+
},
|
|
705
|
+
"tempvars.deleteAllRuntime": {
|
|
706
|
+
opcode: "fxTempVars_deleteAllRuntimeVariables",
|
|
707
|
+
extension: "fxTempVars",
|
|
708
|
+
requiresExtension: true,
|
|
709
|
+
},
|
|
710
|
+
"tempvars.runtimes": {
|
|
711
|
+
opcode: "fxTempVars_listRuntimeVariables",
|
|
712
|
+
kind: "reporter",
|
|
713
|
+
extension: "fxTempVars",
|
|
714
|
+
requiresExtension: true,
|
|
715
|
+
},
|
|
613
716
|
};
|
|
614
717
|
|
|
615
718
|
/**
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { build } from "../../build.js";
|
|
4
|
+
import { outputDirectory, sourceExtension } from "../../brand.js";
|
|
5
|
+
import { slugify } from "../../package.js";
|
|
6
|
+
import { loadProjectState } from "../context.js";
|
|
7
|
+
import { formatDuration } from "../format.js";
|
|
8
|
+
|
|
9
|
+
const EXTENSION = sourceExtension();
|
|
10
|
+
|
|
11
|
+
export const buildCommand = {
|
|
12
|
+
name: "build",
|
|
13
|
+
aliases: ["b"],
|
|
14
|
+
group: "Core Commands:",
|
|
15
|
+
summary: "Compile sources into a .pmp",
|
|
16
|
+
description: `Compile a source file, or every ${EXTENSION} file in a directory
|
|
17
|
+
(defaults to the working directory), into one .pmp archive.`,
|
|
18
|
+
arguments: [`[input${EXTENSION}|dir]`],
|
|
19
|
+
options: [{ flags: "-o, --output <file>", description: "Output .pmp path" }],
|
|
20
|
+
examples: [
|
|
21
|
+
"fx build",
|
|
22
|
+
"fx build src/main.tfx -o build/game.pmp",
|
|
23
|
+
"fx build examples/player --no-config",
|
|
24
|
+
],
|
|
25
|
+
async run({ args, options, cwd, reporter, prompts }) {
|
|
26
|
+
const started = performance.now();
|
|
27
|
+
const state = await loadProjectState({
|
|
28
|
+
input: args[0],
|
|
29
|
+
cwd,
|
|
30
|
+
options,
|
|
31
|
+
reporter,
|
|
32
|
+
prompts,
|
|
33
|
+
});
|
|
34
|
+
const { target, loaded, catalog, extensionUrls } = state;
|
|
35
|
+
|
|
36
|
+
const result = build({
|
|
37
|
+
...target,
|
|
38
|
+
projectConfig: loaded.project,
|
|
39
|
+
fxConfig: loaded.fx,
|
|
40
|
+
catalog,
|
|
41
|
+
extensionUrls,
|
|
42
|
+
useConfigFile: false,
|
|
43
|
+
});
|
|
44
|
+
for (const warning of result.warnings) reporter.warn(warning);
|
|
45
|
+
|
|
46
|
+
const explicit = options.output;
|
|
47
|
+
let output;
|
|
48
|
+
if (explicit) {
|
|
49
|
+
output = path.resolve(cwd, explicit);
|
|
50
|
+
} else if (target.file) {
|
|
51
|
+
output = target.file.toLowerCase().endsWith(EXTENSION.toLowerCase())
|
|
52
|
+
? `${target.file.slice(0, -EXTENSION.length)}.pmp`
|
|
53
|
+
: `${target.file}.pmp`;
|
|
54
|
+
} else {
|
|
55
|
+
output = path.join(
|
|
56
|
+
target.dir,
|
|
57
|
+
outputDirectory(),
|
|
58
|
+
`${slugify(result.config.name || path.basename(target.dir))}.pmp`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
fs.mkdirSync(path.dirname(path.resolve(output)), { recursive: true });
|
|
62
|
+
fs.writeFileSync(output, result.sb3);
|
|
63
|
+
|
|
64
|
+
const blockCount = result.project.targets.reduce(
|
|
65
|
+
(sum, item) => sum + Object.keys(item.blocks).length,
|
|
66
|
+
0,
|
|
67
|
+
);
|
|
68
|
+
const durationMs = Math.round(performance.now() - started);
|
|
69
|
+
|
|
70
|
+
reporter.success(`Built ${output}`);
|
|
71
|
+
reporter.text("");
|
|
72
|
+
reporter.detail("Sources", `${result.files.length} file(s)`);
|
|
73
|
+
reporter.detail("Targets", result.project.targets.length);
|
|
74
|
+
reporter.detail("Blocks", blockCount);
|
|
75
|
+
reporter.detail("Assets", result.assets.size);
|
|
76
|
+
reporter.detail(
|
|
77
|
+
"Extensions",
|
|
78
|
+
result.project.extensions.join(", ") || "(none)",
|
|
79
|
+
);
|
|
80
|
+
reporter.detail("Time", formatDuration(durationMs));
|
|
81
|
+
if (state.unsandboxed.length)
|
|
82
|
+
reporter.detail(
|
|
83
|
+
"Unsandboxed",
|
|
84
|
+
`${state.unsandboxed.join(", ")} (must be allowed in the editor)`,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
output,
|
|
89
|
+
sources: result.files,
|
|
90
|
+
targets: result.project.targets.length,
|
|
91
|
+
blocks: blockCount,
|
|
92
|
+
assets: result.assets.size,
|
|
93
|
+
extensions: result.project.extensions,
|
|
94
|
+
unsandboxed: state.unsandboxed,
|
|
95
|
+
durationMs,
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CATALOG, blockNames } from "../../catalog.js";
|
|
2
|
+
import { loadProjectState } from "../context.js";
|
|
3
|
+
|
|
4
|
+
export const catalogCommand = {
|
|
5
|
+
name: "catalog",
|
|
6
|
+
aliases: ["l", "blocks"],
|
|
7
|
+
group: "Core Commands:",
|
|
8
|
+
summary: "List available blocks",
|
|
9
|
+
description: `List block names, including blocks from extensions configured in the
|
|
10
|
+
project.`,
|
|
11
|
+
arguments: ["[dir]"],
|
|
12
|
+
examples: ["fx catalog", "fx catalog --json"],
|
|
13
|
+
async run({ args, cwd, options, reporter, product, prompts }) {
|
|
14
|
+
const state = await loadProjectState({
|
|
15
|
+
input: args[0],
|
|
16
|
+
cwd,
|
|
17
|
+
options,
|
|
18
|
+
reporter,
|
|
19
|
+
prompts,
|
|
20
|
+
});
|
|
21
|
+
const merged = { ...CATALOG, ...state.catalog };
|
|
22
|
+
const names = Object.keys(merged).sort();
|
|
23
|
+
const builtIn = blockNames().length;
|
|
24
|
+
|
|
25
|
+
reporter.heading(
|
|
26
|
+
`${product.name} catalog (${names.length} blocks` +
|
|
27
|
+
(names.length > builtIn
|
|
28
|
+
? `, ${names.length - builtIn} from extensions`
|
|
29
|
+
: "") +
|
|
30
|
+
"):",
|
|
31
|
+
);
|
|
32
|
+
for (const name of names) {
|
|
33
|
+
const entry = merged[name];
|
|
34
|
+
reporter.item(name, `-> ${entry.opcode} [${entry.kind}]`, {
|
|
35
|
+
width: 32,
|
|
36
|
+
suffix: entry.extension ? `(ext ${entry.extension})` : "",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return merged;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { parse } from "../../parser.js";
|
|
3
|
+
import { discoverSources, mergePrograms } from "../../project.js";
|
|
4
|
+
import { sourceExtension } from "../../brand.js";
|
|
5
|
+
import { loadProjectState } from "../context.js";
|
|
6
|
+
import { UsageError } from "../errors.js";
|
|
7
|
+
|
|
8
|
+
const EXTENSION = sourceExtension();
|
|
9
|
+
|
|
10
|
+
export const checkCommand = {
|
|
11
|
+
name: "check",
|
|
12
|
+
aliases: ["c"],
|
|
13
|
+
group: "Core Commands:",
|
|
14
|
+
summary: "Parse sources and report errors",
|
|
15
|
+
description: `Parse a file, or every ${EXTENSION} file in a directory, without
|
|
16
|
+
building an archive.`,
|
|
17
|
+
arguments: [`[input${EXTENSION}|dir]`],
|
|
18
|
+
examples: ["fx check", "fx check src/main.tfx"],
|
|
19
|
+
async run({ args, cwd, options, reporter }) {
|
|
20
|
+
const state = await loadProjectState({
|
|
21
|
+
input: args[0],
|
|
22
|
+
cwd,
|
|
23
|
+
options,
|
|
24
|
+
reporter,
|
|
25
|
+
includeExtensions: false,
|
|
26
|
+
migrateLegacy: false,
|
|
27
|
+
});
|
|
28
|
+
const { target, loaded } = state;
|
|
29
|
+
const files = target.file ? [target.file] : discoverSources(target.dir);
|
|
30
|
+
if (files.length === 0)
|
|
31
|
+
throw new UsageError(`No ${EXTENSION} files found under ${target.dir}`);
|
|
32
|
+
|
|
33
|
+
const entries = files.map((file) => ({
|
|
34
|
+
ast: parse(fs.readFileSync(file, "utf8"), file),
|
|
35
|
+
file,
|
|
36
|
+
}));
|
|
37
|
+
mergePrograms(entries, {
|
|
38
|
+
partialSprites: loaded.fx?.partialSprites ?? false,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
reporter.success(`Checked ${files.length} file(s): OK`);
|
|
42
|
+
return { files, ok: true };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { completionCandidates } from "../completion/index.js";
|
|
2
|
+
import { bashCompletion } from "../completion/bash.js";
|
|
3
|
+
import { fishCompletion } from "../completion/fish.js";
|
|
4
|
+
import { zshCompletion } from "../completion/zsh.js";
|
|
5
|
+
import { UsageError } from "../errors.js";
|
|
6
|
+
|
|
7
|
+
const SHELLS = new Set(["bash", "zsh", "fish"]);
|
|
8
|
+
|
|
9
|
+
export const completionCommand = {
|
|
10
|
+
name: "completion",
|
|
11
|
+
group: "Utility Commands:",
|
|
12
|
+
summary: "Print shell completion setup",
|
|
13
|
+
description: `Print a completion script for bash, zsh, or fish. Add the script
|
|
14
|
+
to your shell profile, or source it directly.`,
|
|
15
|
+
arguments: ["<shell>"],
|
|
16
|
+
examples: [
|
|
17
|
+
"fx completion bash >> ~/.bashrc",
|
|
18
|
+
"fx completion zsh > ~/.zfunc/_fx",
|
|
19
|
+
"fx completion fish > ~/.config/fish/completions/fx.fish",
|
|
20
|
+
],
|
|
21
|
+
run({ args, reporter, product }) {
|
|
22
|
+
const shell = args[0];
|
|
23
|
+
if (!SHELLS.has(shell))
|
|
24
|
+
throw new UsageError(
|
|
25
|
+
`completion requires one of: ${[...SHELLS].join(", ")}`,
|
|
26
|
+
);
|
|
27
|
+
const command = product.command;
|
|
28
|
+
const script =
|
|
29
|
+
shell === "bash"
|
|
30
|
+
? bashCompletion(command)
|
|
31
|
+
: shell === "zsh"
|
|
32
|
+
? zshCompletion(command)
|
|
33
|
+
: fishCompletion(command);
|
|
34
|
+
reporter.text(script.endsWith("\n") ? script.slice(0, -1) : script);
|
|
35
|
+
return { shell, script };
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const completeCommand = {
|
|
40
|
+
name: "__complete",
|
|
41
|
+
hidden: true,
|
|
42
|
+
summary: "Internal shell completion protocol",
|
|
43
|
+
arguments: ["[words...]"],
|
|
44
|
+
run({ args, command, reporter }) {
|
|
45
|
+
const words = args[0] == null ? [] : args[0];
|
|
46
|
+
const list = Array.isArray(words) ? words : [words];
|
|
47
|
+
const program = command.parent ?? command;
|
|
48
|
+
// The generated shell scripts pass their own name first as a marker.
|
|
49
|
+
const input = SHELLS.has(list[0]) ? list.slice(1) : list;
|
|
50
|
+
const candidates = completionCandidates(program, input);
|
|
51
|
+
if (candidates.length > 0) reporter.text(candidates.join("\n"));
|
|
52
|
+
return { candidates };
|
|
53
|
+
},
|
|
54
|
+
};
|