@vsceasy/cli 0.1.9 → 0.1.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 +32 -0
- package/dist/bin/cli.js +652 -17
- package/dist/index.js +608 -6
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/contributesMerge.d.ts +18 -0
- package/dist/lib/helper/add.d.ts +1 -1
- package/dist/lib/scaffold.d.ts +4 -0
- package/dist/lib/templatesData.d.ts +1 -1
- package/package.json +1 -1
- package/templates/_assets/language/README.language.md +39 -0
- package/templates/_assets/language/contributes.extra.json +40 -0
- package/templates/_assets/language/fileicons/{{langId}}-icon-theme.json +13 -0
- package/templates/_assets/language/icons/{{langId}}.svg +5 -0
- package/templates/_assets/language/language-configuration.json +30 -0
- package/templates/_assets/language/snippets/{{langId}}.json +12 -0
- package/templates/_assets/language/src/colorize.ts +31 -0
- package/templates/_assets/language/src/commands/applyColors.ts +11 -0
- package/templates/_assets/language/src/commands/removeColors.ts +11 -0
- package/templates/_assets/language/src/extension/extension.ts +25 -0
- package/templates/_assets/language/src/helpers/colorize.ts +70 -0
- package/templates/_assets/language/syntaxes/{{langId}}.tmLanguage.json +45 -0
- package/templates/_generators/helper/colorize.ts.tpl +85 -0
- package/templates/react/scripts/gen.ts +55 -0
package/dist/bin/cli.js
CHANGED
|
@@ -3738,15 +3738,26 @@ async function scaffold(opts) {
|
|
|
3738
3738
|
throw new Error(`Target directory not empty: ${opts.targetDir}`);
|
|
3739
3739
|
}
|
|
3740
3740
|
fs2.mkdirSync(opts.targetDir, { recursive: true });
|
|
3741
|
+
const type = opts.type ?? "ui";
|
|
3741
3742
|
const vars = buildVars(opts);
|
|
3742
3743
|
await copyTree(src, opts.targetDir, vars);
|
|
3743
|
-
|
|
3744
|
+
applyType(opts.targetDir, type, opts.preset ?? "full", opts.templatesRoot, vars);
|
|
3744
3745
|
writeConfig(opts.targetDir, {
|
|
3745
3746
|
publisher: opts.publisher,
|
|
3746
3747
|
commandPrefix: vars.commandPrefix,
|
|
3747
|
-
|
|
3748
|
+
type,
|
|
3749
|
+
...type === "ui" ? { ui: opts.ui } : {}
|
|
3748
3750
|
});
|
|
3749
3751
|
}
|
|
3752
|
+
function applyType(targetDir, type, preset, templatesRoot, vars) {
|
|
3753
|
+
if (type === "ui") {
|
|
3754
|
+
applyPreset(targetDir, preset);
|
|
3755
|
+
return;
|
|
3756
|
+
}
|
|
3757
|
+
stripWebview(targetDir);
|
|
3758
|
+
if (type === "language")
|
|
3759
|
+
applyLanguage(targetDir, templatesRoot, vars);
|
|
3760
|
+
}
|
|
3750
3761
|
function applyPreset(targetDir, preset) {
|
|
3751
3762
|
if (preset === "full")
|
|
3752
3763
|
return;
|
|
@@ -3769,15 +3780,104 @@ function applyPreset(targetDir, preset) {
|
|
|
3769
3780
|
`);
|
|
3770
3781
|
}
|
|
3771
3782
|
}
|
|
3783
|
+
function stripWebview(targetDir) {
|
|
3784
|
+
const removals = [
|
|
3785
|
+
"src/panels",
|
|
3786
|
+
"src/webview",
|
|
3787
|
+
"src/commands/hello.ts",
|
|
3788
|
+
"vite.config.ts"
|
|
3789
|
+
];
|
|
3790
|
+
for (const rel of removals) {
|
|
3791
|
+
const abs = path2.join(targetDir, rel);
|
|
3792
|
+
if (fs2.existsSync(abs))
|
|
3793
|
+
fs2.rmSync(abs, { recursive: true, force: true });
|
|
3794
|
+
}
|
|
3795
|
+
const apiPath = path2.join(targetDir, "src", "shared", "api.ts");
|
|
3796
|
+
if (fs2.existsSync(apiPath)) {
|
|
3797
|
+
fs2.writeFileSync(apiPath, `// RPC contracts go here (add a panel with \`vsceasy panel add\`).
|
|
3798
|
+
`);
|
|
3799
|
+
}
|
|
3800
|
+
trimReactFromPackageJson(targetDir);
|
|
3801
|
+
}
|
|
3802
|
+
function trimReactFromPackageJson(targetDir) {
|
|
3803
|
+
const pkgPath = path2.join(targetDir, "package.json");
|
|
3804
|
+
if (!fs2.existsSync(pkgPath))
|
|
3805
|
+
return;
|
|
3806
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
3807
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
3808
|
+
if (!pkg[section])
|
|
3809
|
+
continue;
|
|
3810
|
+
for (const dep of Object.keys(pkg[section])) {
|
|
3811
|
+
if (REACT_DEPS.has(dep))
|
|
3812
|
+
delete pkg[section][dep];
|
|
3813
|
+
}
|
|
3814
|
+
if (Object.keys(pkg[section]).length === 0)
|
|
3815
|
+
delete pkg[section];
|
|
3816
|
+
}
|
|
3817
|
+
if (pkg.scripts) {
|
|
3818
|
+
for (const key of Object.keys(pkg.scripts)) {
|
|
3819
|
+
if (UI_SCRIPT_KEYS.has(key))
|
|
3820
|
+
delete pkg.scripts[key];
|
|
3821
|
+
}
|
|
3822
|
+
if (pkg.scripts.dev) {
|
|
3823
|
+
pkg.scripts.dev = "bun run gen:scan && bun run dev:ext";
|
|
3824
|
+
}
|
|
3825
|
+
for (const [k, v] of Object.entries(pkg.scripts)) {
|
|
3826
|
+
pkg.scripts[k] = v.replace(/\s*&&\s*bun run build:ui/g, "");
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + `
|
|
3830
|
+
`);
|
|
3831
|
+
}
|
|
3832
|
+
function applyLanguage(targetDir, templatesRoot, vars) {
|
|
3833
|
+
const assetsDir = path2.join(templatesRoot, "_assets", "language");
|
|
3834
|
+
if (!fs2.existsSync(assetsDir)) {
|
|
3835
|
+
throw new Error(`Language assets not found: ${assetsDir}`);
|
|
3836
|
+
}
|
|
3837
|
+
copyAssetsTree(assetsDir, targetDir, vars);
|
|
3838
|
+
const langReadme = path2.join(targetDir, "README.language.md");
|
|
3839
|
+
if (fs2.existsSync(langReadme)) {
|
|
3840
|
+
fs2.renameSync(langReadme, path2.join(targetDir, "README.md"));
|
|
3841
|
+
}
|
|
3842
|
+
const pkgPath = path2.join(targetDir, "package.json");
|
|
3843
|
+
if (fs2.existsSync(pkgPath)) {
|
|
3844
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
3845
|
+
pkg.activationEvents = [`onLanguage:${vars.langId}`];
|
|
3846
|
+
fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + `
|
|
3847
|
+
`);
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
function copyAssetsTree(srcDir, destDir, vars) {
|
|
3851
|
+
for (const entry of fs2.readdirSync(srcDir, { withFileTypes: true })) {
|
|
3852
|
+
if (SKIP_NAMES.has(entry.name))
|
|
3853
|
+
continue;
|
|
3854
|
+
const srcPath = path2.join(srcDir, entry.name);
|
|
3855
|
+
const destName = substitute(entry.name, vars);
|
|
3856
|
+
const destPath = path2.join(destDir, destName);
|
|
3857
|
+
if (entry.isDirectory()) {
|
|
3858
|
+
fs2.mkdirSync(destPath, { recursive: true });
|
|
3859
|
+
copyAssetsTree(srcPath, destPath, vars);
|
|
3860
|
+
} else if (entry.isFile()) {
|
|
3861
|
+
fs2.mkdirSync(path2.dirname(destPath), { recursive: true });
|
|
3862
|
+
fs2.writeFileSync(destPath, substitute(fs2.readFileSync(srcPath, "utf8"), vars));
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3772
3866
|
function buildVars(opts) {
|
|
3773
3867
|
const simpleName = opts.name.replace(/^@[^/]+\//, "");
|
|
3774
3868
|
const commandPrefix = simpleName.replace(/[^a-zA-Z0-9]+/g, "");
|
|
3869
|
+
const langId = simpleName.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
3870
|
+
const scopeName = `source.${langId}`;
|
|
3871
|
+
const langExt = langId.slice(0, 4).toUpperCase();
|
|
3775
3872
|
return {
|
|
3776
3873
|
name: opts.name,
|
|
3777
3874
|
displayName: opts.displayName,
|
|
3778
3875
|
description: opts.description,
|
|
3779
3876
|
publisher: opts.publisher,
|
|
3780
|
-
commandPrefix
|
|
3877
|
+
commandPrefix,
|
|
3878
|
+
langId,
|
|
3879
|
+
scopeName,
|
|
3880
|
+
langExt
|
|
3781
3881
|
};
|
|
3782
3882
|
}
|
|
3783
3883
|
async function copyTree(srcDir, destDir, vars) {
|
|
@@ -3803,7 +3903,7 @@ async function copyTree(srcDir, destDir, vars) {
|
|
|
3803
3903
|
function substitute(input, vars) {
|
|
3804
3904
|
return input.replace(/\{\{(\w+)\}\}/g, (_m, key) => vars[key] ?? `{{${key}}}`);
|
|
3805
3905
|
}
|
|
3806
|
-
var fs2, path2, PLACEHOLDER_EXTS, SKIP_NAMES;
|
|
3906
|
+
var fs2, path2, PLACEHOLDER_EXTS, SKIP_NAMES, REACT_DEPS, UI_SCRIPT_KEYS;
|
|
3807
3907
|
var init_scaffold = __esm(() => {
|
|
3808
3908
|
init_config();
|
|
3809
3909
|
fs2 = __toESM(require("fs"));
|
|
@@ -3823,12 +3923,326 @@ var init_scaffold = __esm(() => {
|
|
|
3823
3923
|
".yaml"
|
|
3824
3924
|
]);
|
|
3825
3925
|
SKIP_NAMES = new Set(["node_modules", "dist", ".DS_Store"]);
|
|
3926
|
+
REACT_DEPS = new Set([
|
|
3927
|
+
"react",
|
|
3928
|
+
"react-dom",
|
|
3929
|
+
"@types/react",
|
|
3930
|
+
"@types/react-dom",
|
|
3931
|
+
"@vitejs/plugin-react",
|
|
3932
|
+
"vite"
|
|
3933
|
+
]);
|
|
3934
|
+
UI_SCRIPT_KEYS = new Set(["dev:ui", "build:ui"]);
|
|
3826
3935
|
});
|
|
3827
3936
|
|
|
3828
3937
|
// src/lib/templatesData.ts
|
|
3829
|
-
var TEMPLATES_VERSION = "0.1.
|
|
3938
|
+
var TEMPLATES_VERSION = "0.1.10", TEMPLATE_FILES;
|
|
3830
3939
|
var init_templatesData = __esm(() => {
|
|
3831
3940
|
TEMPLATE_FILES = {
|
|
3941
|
+
"_assets/language/README.language.md": "## {{displayName}} language support\n\nThis extension was scaffolded with `vsceasy create --type language`. It provides\neditor support for `.{{langId}}` files:\n\n- **Syntax highlighting** — TextMate grammar in `syntaxes/{{langId}}.tmLanguage.json`\n- **Language configuration** — comments, brackets, auto-closing pairs, folding in\n `language-configuration.json`\n- **Snippets** — `snippets/{{langId}}.json`\n- **File icon** (opt-in) — `fileicons/{{langId}}-icon-theme.json`\n\n### How contributions are wired\n\n`vsceasy`'s `scripts/gen.ts` regenerates the generated parts of\n`package.json#contributes` (commands, views…) on every build. Language\ncontributions are **not** generated — they live in **`contributes.extra.json`**\nat the project root and are deep-merged into `package.json` by `gen.ts`. Edit\n`contributes.extra.json` to change languages / grammars / snippets / iconThemes,\nthen run `bun run gen`.\n\n### File icon is opt-in\n\nVS Code file icons are provided by an **icon theme**, which is global: activating\nit replaces *all* file icons in the workbench, not just `.{{langId}}`. This\nextension ships a `{{displayName}} Icons` theme but does **not** force it. To use\nit: `Preferences: File Icon Theme` → pick `{{displayName}} Icons`. If you don't\nwant to override every icon, leave it unselected — highlighting, config and\nsnippets work regardless.\n\n### Develop\n\n```sh\nbun install\nbun run gen # sync package.json#contributes\nbun run package # build a .vsix\n```\n\nPress `F5` (or run `bun run launch`) to open an Extension Development Host and\nopen a `.{{langId}}` file to see highlighting.\n",
|
|
3942
|
+
"_assets/language/contributes.extra.json": `{
|
|
3943
|
+
"languages": [
|
|
3944
|
+
{
|
|
3945
|
+
"id": "{{langId}}",
|
|
3946
|
+
"aliases": ["{{displayName}}", "{{langId}}"],
|
|
3947
|
+
"extensions": [".{{langId}}"],
|
|
3948
|
+
"configuration": "./language-configuration.json"
|
|
3949
|
+
}
|
|
3950
|
+
],
|
|
3951
|
+
"grammars": [
|
|
3952
|
+
{
|
|
3953
|
+
"language": "{{langId}}",
|
|
3954
|
+
"scopeName": "{{scopeName}}",
|
|
3955
|
+
"path": "./syntaxes/{{langId}}.tmLanguage.json"
|
|
3956
|
+
}
|
|
3957
|
+
],
|
|
3958
|
+
"snippets": [
|
|
3959
|
+
{
|
|
3960
|
+
"language": "{{langId}}",
|
|
3961
|
+
"path": "./snippets/{{langId}}.json"
|
|
3962
|
+
}
|
|
3963
|
+
],
|
|
3964
|
+
"iconThemes": [
|
|
3965
|
+
{
|
|
3966
|
+
"id": "{{langId}}-icons",
|
|
3967
|
+
"label": "{{displayName}} Icons",
|
|
3968
|
+
"path": "./fileicons/{{langId}}-icon-theme.json"
|
|
3969
|
+
}
|
|
3970
|
+
],
|
|
3971
|
+
"configuration": {
|
|
3972
|
+
"title": "{{displayName}}",
|
|
3973
|
+
"properties": {
|
|
3974
|
+
"{{commandPrefix}}.colorize": {
|
|
3975
|
+
"type": "boolean",
|
|
3976
|
+
"default": true,
|
|
3977
|
+
"markdownDescription": "Automatically apply distinct section colors to \`.{{langId}}\` files (scoped to \`{{scopeName}}\`, so other languages are unaffected). Turn off to keep your theme's default colors."
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
`,
|
|
3983
|
+
"_assets/language/fileicons/{{langId}}-icon-theme.json": `{
|
|
3984
|
+
"iconDefinitions": {
|
|
3985
|
+
"{{langId}}_file": {
|
|
3986
|
+
"iconPath": "../icons/{{langId}}.svg"
|
|
3987
|
+
}
|
|
3988
|
+
},
|
|
3989
|
+
"languageIds": {
|
|
3990
|
+
"{{langId}}": "{{langId}}_file"
|
|
3991
|
+
},
|
|
3992
|
+
"fileExtensions": {
|
|
3993
|
+
"{{langId}}": "{{langId}}_file"
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
`,
|
|
3997
|
+
"_assets/language/icons/{{langId}}.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
|
3998
|
+
<path fill="#8a8a8a" d="M6 3h14l6 6v20a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/>
|
|
3999
|
+
<path fill="#5a5a5a" d="M20 3l6 6h-6z"/>
|
|
4000
|
+
<text x="16" y="22" font-family="monospace" font-size="9" font-weight="bold" fill="#fff" text-anchor="middle">{{langExt}}</text>
|
|
4001
|
+
</svg>
|
|
4002
|
+
`,
|
|
4003
|
+
"_assets/language/language-configuration.json": `{
|
|
4004
|
+
"comments": {
|
|
4005
|
+
"lineComment": "#"
|
|
4006
|
+
},
|
|
4007
|
+
"brackets": [
|
|
4008
|
+
["{", "}"],
|
|
4009
|
+
["[", "]"],
|
|
4010
|
+
["(", ")"]
|
|
4011
|
+
],
|
|
4012
|
+
"autoClosingPairs": [
|
|
4013
|
+
{ "open": "{", "close": "}" },
|
|
4014
|
+
{ "open": "[", "close": "]" },
|
|
4015
|
+
{ "open": "(", "close": ")" },
|
|
4016
|
+
{ "open": "\\"", "close": "\\"", "notIn": ["string"] },
|
|
4017
|
+
{ "open": "'", "close": "'", "notIn": ["string"] }
|
|
4018
|
+
],
|
|
4019
|
+
"surroundingPairs": [
|
|
4020
|
+
["{", "}"],
|
|
4021
|
+
["[", "]"],
|
|
4022
|
+
["(", ")"],
|
|
4023
|
+
["\\"", "\\""],
|
|
4024
|
+
["'", "'"]
|
|
4025
|
+
],
|
|
4026
|
+
"folding": {
|
|
4027
|
+
"markers": {
|
|
4028
|
+
"start": "^\\\\s*#\\\\s*region\\\\b",
|
|
4029
|
+
"end": "^\\\\s*#\\\\s*endregion\\\\b"
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
`,
|
|
4034
|
+
"_assets/language/snippets/{{langId}}.json": `{
|
|
4035
|
+
"Section": {
|
|
4036
|
+
"prefix": "section",
|
|
4037
|
+
"body": ["[\${1:name}]", "$0"],
|
|
4038
|
+
"description": "A {{displayName}} section"
|
|
4039
|
+
},
|
|
4040
|
+
"Key/Value": {
|
|
4041
|
+
"prefix": "kv",
|
|
4042
|
+
"body": ["\${1:key} = \${2:value}"],
|
|
4043
|
+
"description": "A key/value pair"
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
`,
|
|
4047
|
+
"_assets/language/src/colorize.ts": `import type * as vscode from 'vscode';
|
|
4048
|
+
import { applyTokenColors, removeTokenColors, type TokenColorRule } from './helpers/colorize';
|
|
4049
|
+
|
|
4050
|
+
/** Root TextMate scope of this language — rules are applied only to these files. */
|
|
4051
|
+
export const SCOPE = '{{scopeName}}';
|
|
4052
|
+
|
|
4053
|
+
/**
|
|
4054
|
+
* Default token colors for {{displayName}}, emphasizing each construct. Edit
|
|
4055
|
+
* freely — they are applied to \`[{{scopeName}}]\` only, so other languages keep
|
|
4056
|
+
* the user's theme. Scope names must match your grammar
|
|
4057
|
+
* (syntaxes/{{langId}}.tmLanguage.json).
|
|
4058
|
+
*/
|
|
4059
|
+
export const RULES: TokenColorRule[] = [
|
|
4060
|
+
{ scope: 'comment.line.number-sign.{{langId}}', settings: { foreground: '#6b7a6e', fontStyle: 'italic' } },
|
|
4061
|
+
{ scope: 'string.quoted.double.basic.{{langId}}', settings: { foreground: '#98c379' } },
|
|
4062
|
+
{ scope: 'string.quoted.single.literal.{{langId}}', settings: { foreground: '#98c379' } },
|
|
4063
|
+
{ scope: 'constant.numeric.{{langId}}', settings: { foreground: '#d19a66' } },
|
|
4064
|
+
];
|
|
4065
|
+
|
|
4066
|
+
export async function applyColors(vscodeNs: typeof vscode): Promise<void> {
|
|
4067
|
+
await applyTokenColors(SCOPE, RULES);
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
export async function removeColors(vscodeNs: typeof vscode): Promise<void> {
|
|
4071
|
+
await removeTokenColors(SCOPE);
|
|
4072
|
+
}
|
|
4073
|
+
|
|
4074
|
+
/** True when the user has opted in (default) to automatic coloring. */
|
|
4075
|
+
export function colorizeEnabled(vscodeNs: typeof vscode): boolean {
|
|
4076
|
+
return vscodeNs.workspace.getConfiguration('{{commandPrefix}}').get<boolean>('colorize', true);
|
|
4077
|
+
}
|
|
4078
|
+
`,
|
|
4079
|
+
"_assets/language/src/commands/applyColors.ts": `import { defineCommand } from '../shared/vsceasy';
|
|
4080
|
+
import { applyColors } from '../colorize';
|
|
4081
|
+
|
|
4082
|
+
export default defineCommand({
|
|
4083
|
+
id: 'applyColors',
|
|
4084
|
+
title: '{{displayName}}: Apply Colors',
|
|
4085
|
+
run: async (vscode) => {
|
|
4086
|
+
await applyColors(vscode);
|
|
4087
|
+
vscode.window.showInformationMessage('{{displayName}} colors applied.');
|
|
4088
|
+
},
|
|
4089
|
+
});
|
|
4090
|
+
`,
|
|
4091
|
+
"_assets/language/src/commands/removeColors.ts": `import { defineCommand } from '../shared/vsceasy';
|
|
4092
|
+
import { removeColors } from '../colorize';
|
|
4093
|
+
|
|
4094
|
+
export default defineCommand({
|
|
4095
|
+
id: 'removeColors',
|
|
4096
|
+
title: '{{displayName}}: Remove Colors',
|
|
4097
|
+
run: async (vscode) => {
|
|
4098
|
+
await removeColors(vscode);
|
|
4099
|
+
vscode.window.showInformationMessage('{{displayName}} colors removed.');
|
|
4100
|
+
},
|
|
4101
|
+
});
|
|
4102
|
+
`,
|
|
4103
|
+
"_assets/language/src/extension/extension.ts": `import { bootstrap } from '../shared/vsceasy';
|
|
4104
|
+
import { registry } from './_registry';
|
|
4105
|
+
import { applyColors, removeColors, colorizeEnabled } from '../colorize';
|
|
4106
|
+
|
|
4107
|
+
export const activate = bootstrap(registry, {
|
|
4108
|
+
onActivate: [
|
|
4109
|
+
async (context, vscode) => {
|
|
4110
|
+
// Auto-apply scoped token colors on activate when opted in (default).
|
|
4111
|
+
// Scoped to {{scopeName}} only — other languages are untouched.
|
|
4112
|
+
if (colorizeEnabled(vscode)) {
|
|
4113
|
+
await applyColors(vscode);
|
|
4114
|
+
}
|
|
4115
|
+
// React to the user toggling \`{{commandPrefix}}.colorize\` at runtime.
|
|
4116
|
+
context.subscriptions.push(
|
|
4117
|
+
vscode.workspace.onDidChangeConfiguration(async (e) => {
|
|
4118
|
+
if (!e.affectsConfiguration('{{commandPrefix}}.colorize')) return;
|
|
4119
|
+
if (colorizeEnabled(vscode)) await applyColors(vscode);
|
|
4120
|
+
else await removeColors(vscode);
|
|
4121
|
+
}),
|
|
4122
|
+
);
|
|
4123
|
+
},
|
|
4124
|
+
],
|
|
4125
|
+
});
|
|
4126
|
+
|
|
4127
|
+
export function deactivate() {}
|
|
4128
|
+
`,
|
|
4129
|
+
"_assets/language/src/helpers/colorize.ts": `import * as vscode from 'vscode';
|
|
4130
|
+
|
|
4131
|
+
/**
|
|
4132
|
+
* Apply theme-independent token colors to a single TextMate scope (e.g. a
|
|
4133
|
+
* language's root scope like \`{{scopeName}}\`), written to the user's
|
|
4134
|
+
* \`editor.tokenColorCustomizations\`. Because the rules are keyed by
|
|
4135
|
+
* \`[<scope>]\`, only files in that scope are recolored — every other language
|
|
4136
|
+
* keeps the active theme's colors.
|
|
4137
|
+
*
|
|
4138
|
+
* Rules are tagged with a marker so {@link removeTokenColors} can strip exactly
|
|
4139
|
+
* the ones this extension added, preserving any the user wrote by hand.
|
|
4140
|
+
*/
|
|
4141
|
+
|
|
4142
|
+
export interface TokenColorRule {
|
|
4143
|
+
/** Comma-separated TextMate scopes, e.g. 'entity.name.section.foo, comment.line.foo'. */
|
|
4144
|
+
scope: string;
|
|
4145
|
+
settings: { foreground?: string; background?: string; fontStyle?: string };
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
type TaggedRule = TokenColorRule & { [MARK]?: true };
|
|
4149
|
+
|
|
4150
|
+
/** Marker key identifying rules this extension wrote (vs. the user's own). */
|
|
4151
|
+
const MARK = '{{commandPrefix}}Colorize';
|
|
4152
|
+
const SECTION = 'editor.tokenColorCustomizations';
|
|
4153
|
+
|
|
4154
|
+
const blockKey = (scope: string) => \`[\${scope}]\`;
|
|
4155
|
+
|
|
4156
|
+
/**
|
|
4157
|
+
* Merge \`rules\` into \`editor.tokenColorCustomizations["[<scope>]"].textMateRules\`,
|
|
4158
|
+
* preserving the user's own rules and other scope keys. Idempotent — re-applying
|
|
4159
|
+
* replaces only previously-applied rules from this extension.
|
|
4160
|
+
*/
|
|
4161
|
+
export async function applyTokenColors(
|
|
4162
|
+
scope: string,
|
|
4163
|
+
rules: TokenColorRule[],
|
|
4164
|
+
target: vscode.ConfigurationTarget = vscode.ConfigurationTarget.Global,
|
|
4165
|
+
): Promise<void> {
|
|
4166
|
+
const cfg = vscode.workspace.getConfiguration();
|
|
4167
|
+
const current = (cfg.get<Record<string, any>>(SECTION) ?? {}) as Record<string, any>;
|
|
4168
|
+
const key = blockKey(scope);
|
|
4169
|
+
const block = (current[key] ?? {}) as { textMateRules?: TaggedRule[] };
|
|
4170
|
+
const existing = Array.isArray(block.textMateRules) ? block.textMateRules : [];
|
|
4171
|
+
const userRules = existing.filter((r) => !r[MARK]);
|
|
4172
|
+
const ours: TaggedRule[] = rules.map((r) => ({ ...r, [MARK]: true }));
|
|
4173
|
+
const next = { ...current, [key]: { ...block, textMateRules: [...userRules, ...ours] } };
|
|
4174
|
+
await cfg.update(SECTION, next, target);
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4177
|
+
/** Remove only the rules this extension added for \`scope\`; leave the rest intact. */
|
|
4178
|
+
export async function removeTokenColors(
|
|
4179
|
+
scope: string,
|
|
4180
|
+
target: vscode.ConfigurationTarget = vscode.ConfigurationTarget.Global,
|
|
4181
|
+
): Promise<void> {
|
|
4182
|
+
const cfg = vscode.workspace.getConfiguration();
|
|
4183
|
+
const current = cfg.get<Record<string, any>>(SECTION);
|
|
4184
|
+
const key = blockKey(scope);
|
|
4185
|
+
if (!current || !current[key]) return;
|
|
4186
|
+
const block = current[key] as { textMateRules?: TaggedRule[] };
|
|
4187
|
+
const userRules = (block.textMateRules ?? []).filter((r) => !r[MARK]);
|
|
4188
|
+
|
|
4189
|
+
const nextBlock: Record<string, unknown> = { ...block };
|
|
4190
|
+
if (userRules.length) nextBlock.textMateRules = userRules;
|
|
4191
|
+
else delete nextBlock.textMateRules;
|
|
4192
|
+
|
|
4193
|
+
const next = { ...current };
|
|
4194
|
+
if (Object.keys(nextBlock).length) next[key] = nextBlock;
|
|
4195
|
+
else delete next[key];
|
|
4196
|
+
|
|
4197
|
+
await cfg.update(SECTION, Object.keys(next).length ? next : undefined, target);
|
|
4198
|
+
}
|
|
4199
|
+
`,
|
|
4200
|
+
"_assets/language/syntaxes/{{langId}}.tmLanguage.json": `{
|
|
4201
|
+
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
|
4202
|
+
"name": "{{displayName}}",
|
|
4203
|
+
"scopeName": "{{scopeName}}",
|
|
4204
|
+
"patterns": [
|
|
4205
|
+
{ "include": "#comments" },
|
|
4206
|
+
{ "include": "#strings" },
|
|
4207
|
+
{ "include": "#numbers" }
|
|
4208
|
+
],
|
|
4209
|
+
"repository": {
|
|
4210
|
+
"comments": {
|
|
4211
|
+
"patterns": [
|
|
4212
|
+
{
|
|
4213
|
+
"name": "comment.line.number-sign.{{langId}}",
|
|
4214
|
+
"match": "#.*$"
|
|
4215
|
+
}
|
|
4216
|
+
]
|
|
4217
|
+
},
|
|
4218
|
+
"strings": {
|
|
4219
|
+
"patterns": [
|
|
4220
|
+
{
|
|
4221
|
+
"name": "string.quoted.double.{{langId}}",
|
|
4222
|
+
"begin": "\\"",
|
|
4223
|
+
"end": "\\"",
|
|
4224
|
+
"patterns": [
|
|
4225
|
+
{ "name": "constant.character.escape.{{langId}}", "match": "\\\\\\\\." }
|
|
4226
|
+
]
|
|
4227
|
+
},
|
|
4228
|
+
{
|
|
4229
|
+
"name": "string.quoted.single.{{langId}}",
|
|
4230
|
+
"begin": "'",
|
|
4231
|
+
"end": "'"
|
|
4232
|
+
}
|
|
4233
|
+
]
|
|
4234
|
+
},
|
|
4235
|
+
"numbers": {
|
|
4236
|
+
"patterns": [
|
|
4237
|
+
{
|
|
4238
|
+
"name": "constant.numeric.{{langId}}",
|
|
4239
|
+
"match": "\\\\b[0-9]+(\\\\.[0-9]+)?\\\\b"
|
|
4240
|
+
}
|
|
4241
|
+
]
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
`,
|
|
3832
4246
|
"_generators/command/command.ts.tpl": `import { defineCommand } from '../shared/vsceasy';
|
|
3833
4247
|
|
|
3834
4248
|
export default defineCommand({
|
|
@@ -4436,6 +4850,92 @@ export function createCache<V = unknown>(opts: CacheOptions = {}): Cache<V> {
|
|
|
4436
4850
|
|
|
4437
4851
|
return cache;
|
|
4438
4852
|
}
|
|
4853
|
+
`,
|
|
4854
|
+
"_generators/helper/colorize.ts.tpl": `import * as vscode from 'vscode';
|
|
4855
|
+
|
|
4856
|
+
/**
|
|
4857
|
+
* Apply theme-independent token colors to a single TextMate scope (e.g. a
|
|
4858
|
+
* language's root scope like \`source.toml\`), written to the user's
|
|
4859
|
+
* \`editor.tokenColorCustomizations\`. Because the rules are keyed by
|
|
4860
|
+
* \`[<scope>]\`, only files in that scope are recolored — every other language
|
|
4861
|
+
* keeps the active theme's colors.
|
|
4862
|
+
*
|
|
4863
|
+
* Rules are tagged with a marker so {@link removeTokenColors} can strip exactly
|
|
4864
|
+
* the ones this extension added, preserving any the user wrote by hand.
|
|
4865
|
+
*
|
|
4866
|
+
* Typical use — auto-apply on activate behind an opt-out setting:
|
|
4867
|
+
*
|
|
4868
|
+
* // extension.ts (onActivate hook)
|
|
4869
|
+
* if (config.get<boolean>('colorize', true)) {
|
|
4870
|
+
* await applyTokenColors('source.{{commandPrefix}}', MY_RULES);
|
|
4871
|
+
* }
|
|
4872
|
+
* vscode.workspace.onDidChangeConfiguration(async (e) => {
|
|
4873
|
+
* if (!e.affectsConfiguration('{{commandPrefix}}.colorize')) return;
|
|
4874
|
+
* if (config.get<boolean>('colorize', true)) await applyTokenColors('source.{{commandPrefix}}', MY_RULES);
|
|
4875
|
+
* else await removeTokenColors('source.{{commandPrefix}}');
|
|
4876
|
+
* });
|
|
4877
|
+
*
|
|
4878
|
+
* Declare the opt-out in package.json#contributes.configuration:
|
|
4879
|
+
* "{{commandPrefix}}.colorize": { "type": "boolean", "default": true }
|
|
4880
|
+
*/
|
|
4881
|
+
|
|
4882
|
+
export interface TokenColorRule {
|
|
4883
|
+
/** Comma-separated TextMate scopes, e.g. 'entity.name.section.foo, comment.line.foo'. */
|
|
4884
|
+
scope: string;
|
|
4885
|
+
settings: { foreground?: string; background?: string; fontStyle?: string };
|
|
4886
|
+
}
|
|
4887
|
+
|
|
4888
|
+
type TaggedRule = TokenColorRule & { [MARK]?: true };
|
|
4889
|
+
|
|
4890
|
+
/** Marker key identifying rules this extension wrote (vs. the user's own). */
|
|
4891
|
+
const MARK = '{{commandPrefix}}Colorize';
|
|
4892
|
+
const SECTION = 'editor.tokenColorCustomizations';
|
|
4893
|
+
|
|
4894
|
+
const blockKey = (scope: string) => \`[\${scope}]\`;
|
|
4895
|
+
|
|
4896
|
+
/**
|
|
4897
|
+
* Merge \`rules\` into \`editor.tokenColorCustomizations["[<scope>]"].textMateRules\`,
|
|
4898
|
+
* preserving the user's own rules and other scope keys. Idempotent — re-applying
|
|
4899
|
+
* replaces only previously-applied rules from this extension.
|
|
4900
|
+
*/
|
|
4901
|
+
export async function applyTokenColors(
|
|
4902
|
+
scope: string,
|
|
4903
|
+
rules: TokenColorRule[],
|
|
4904
|
+
target: vscode.ConfigurationTarget = vscode.ConfigurationTarget.Global,
|
|
4905
|
+
): Promise<void> {
|
|
4906
|
+
const cfg = vscode.workspace.getConfiguration();
|
|
4907
|
+
const current = (cfg.get<Record<string, any>>(SECTION) ?? {}) as Record<string, any>;
|
|
4908
|
+
const key = blockKey(scope);
|
|
4909
|
+
const block = (current[key] ?? {}) as { textMateRules?: TaggedRule[] };
|
|
4910
|
+
const existing = Array.isArray(block.textMateRules) ? block.textMateRules : [];
|
|
4911
|
+
const userRules = existing.filter((r) => !r[MARK]);
|
|
4912
|
+
const ours: TaggedRule[] = rules.map((r) => ({ ...r, [MARK]: true }));
|
|
4913
|
+
const next = { ...current, [key]: { ...block, textMateRules: [...userRules, ...ours] } };
|
|
4914
|
+
await cfg.update(SECTION, next, target);
|
|
4915
|
+
}
|
|
4916
|
+
|
|
4917
|
+
/** Remove only the rules this extension added for \`scope\`; leave the rest intact. */
|
|
4918
|
+
export async function removeTokenColors(
|
|
4919
|
+
scope: string,
|
|
4920
|
+
target: vscode.ConfigurationTarget = vscode.ConfigurationTarget.Global,
|
|
4921
|
+
): Promise<void> {
|
|
4922
|
+
const cfg = vscode.workspace.getConfiguration();
|
|
4923
|
+
const current = cfg.get<Record<string, any>>(SECTION);
|
|
4924
|
+
const key = blockKey(scope);
|
|
4925
|
+
if (!current || !current[key]) return;
|
|
4926
|
+
const block = current[key] as { textMateRules?: TaggedRule[] };
|
|
4927
|
+
const userRules = (block.textMateRules ?? []).filter((r) => !r[MARK]);
|
|
4928
|
+
|
|
4929
|
+
const nextBlock: Record<string, unknown> = { ...block };
|
|
4930
|
+
if (userRules.length) nextBlock.textMateRules = userRules;
|
|
4931
|
+
else delete nextBlock.textMateRules;
|
|
4932
|
+
|
|
4933
|
+
const next = { ...current };
|
|
4934
|
+
if (Object.keys(nextBlock).length) next[key] = nextBlock;
|
|
4935
|
+
else delete next[key];
|
|
4936
|
+
|
|
4937
|
+
await cfg.update(SECTION, Object.keys(next).length ? next : undefined, target);
|
|
4938
|
+
}
|
|
4439
4939
|
`,
|
|
4440
4940
|
"_generators/helper/config.ts.tpl": `import * as vscode from 'vscode';
|
|
4441
4941
|
|
|
@@ -5754,6 +6254,12 @@ bun run package # → {{name}}-0.0.1.vsix
|
|
|
5754
6254
|
"react/scripts/gen.ts": `#!/usr/bin/env bun
|
|
5755
6255
|
// Scans src/panels, src/commands, and src/menus; writes src/extension/_registry.ts
|
|
5756
6256
|
// and syncs package.json#contributes (commands, viewsContainers, views).
|
|
6257
|
+
//
|
|
6258
|
+
// Non-generated contributes (e.g. languages, grammars, snippets, themes,
|
|
6259
|
+
// iconThemes, walkthroughs) go in an optional \`contributes.extra.json\` at the
|
|
6260
|
+
// project root. It is deep-merged into package.json#contributes on every run —
|
|
6261
|
+
// the keys gen.ts owns (commands, keybindings, menus.commandPalette,
|
|
6262
|
+
// viewsContainers, views) always win, everything else from extra is preserved.
|
|
5757
6263
|
|
|
5758
6264
|
import * as fs from 'fs';
|
|
5759
6265
|
import * as path from 'path';
|
|
@@ -5769,6 +6275,10 @@ const TREE_VIEWS_DIR = path.join(SRC, 'treeViews');
|
|
|
5769
6275
|
const JOBS_DIR = path.join(SRC, 'jobs');
|
|
5770
6276
|
const OUT = path.join(SRC, 'extension', '_registry.ts');
|
|
5771
6277
|
const PKG_PATH = path.join(ROOT, 'package.json');
|
|
6278
|
+
const EXTRA_PATH = path.join(ROOT, 'contributes.extra.json');
|
|
6279
|
+
|
|
6280
|
+
/** Keys gen.ts owns — never overridden by contributes.extra.json. */
|
|
6281
|
+
const GEN_OWNED_KEYS = new Set(['commands', 'keybindings', 'viewsContainers', 'views']);
|
|
5772
6282
|
|
|
5773
6283
|
interface Discovered {
|
|
5774
6284
|
id: string;
|
|
@@ -5957,9 +6467,54 @@ function syncPackageJson(
|
|
|
5957
6467
|
delete contributes.views;
|
|
5958
6468
|
}
|
|
5959
6469
|
|
|
6470
|
+
mergeExtraContributes(contributes);
|
|
6471
|
+
|
|
5960
6472
|
fs.writeFileSync(PKG_PATH, JSON.stringify(pkg, null, 2) + '\\n');
|
|
5961
6473
|
}
|
|
5962
6474
|
|
|
6475
|
+
/**
|
|
6476
|
+
* Deep-merge the optional \`contributes.extra.json\` (project root) into the
|
|
6477
|
+
* package's \`contributes\`. Use for any contribution point gen.ts doesn't
|
|
6478
|
+
* generate — languages, grammars, snippets, themes, iconThemes, walkthroughs…
|
|
6479
|
+
*
|
|
6480
|
+
* Rules:
|
|
6481
|
+
* - gen-owned keys (commands, keybindings, viewsContainers, views) are ignored
|
|
6482
|
+
* if present in extra — gen.ts stays authoritative for those.
|
|
6483
|
+
* - plain objects merge recursively; arrays and primitives from extra replace.
|
|
6484
|
+
*
|
|
6485
|
+
* NOTE: this is an inline copy of src/lib/contributesMerge.ts in the vsceasy
|
|
6486
|
+
* source (the script must run standalone). Keep the two in sync.
|
|
6487
|
+
*/
|
|
6488
|
+
function mergeExtraContributes(contributes: Record<string, any>) {
|
|
6489
|
+
if (!fs.existsSync(EXTRA_PATH)) return;
|
|
6490
|
+
let extra: Record<string, any>;
|
|
6491
|
+
try {
|
|
6492
|
+
extra = JSON.parse(fs.readFileSync(EXTRA_PATH, 'utf8'));
|
|
6493
|
+
} catch (err) {
|
|
6494
|
+
console.warn(\`! Skipping contributes.extra.json — invalid JSON: \${(err as Error).message}\`);
|
|
6495
|
+
return;
|
|
6496
|
+
}
|
|
6497
|
+
if (!extra || typeof extra !== 'object') return;
|
|
6498
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
6499
|
+
if (GEN_OWNED_KEYS.has(key)) continue;
|
|
6500
|
+
contributes[key] = deepMerge(contributes[key], value);
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
|
|
6504
|
+
function isPlainObject(v: unknown): v is Record<string, any> {
|
|
6505
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
6506
|
+
}
|
|
6507
|
+
|
|
6508
|
+
function deepMerge(base: any, override: any): any {
|
|
6509
|
+
if (isPlainObject(base) && isPlainObject(override)) {
|
|
6510
|
+
const out: Record<string, any> = { ...base };
|
|
6511
|
+
for (const [k, v] of Object.entries(override)) out[k] = deepMerge(base[k], v);
|
|
6512
|
+
return out;
|
|
6513
|
+
}
|
|
6514
|
+
// arrays and primitives: override wins
|
|
6515
|
+
return override;
|
|
6516
|
+
}
|
|
6517
|
+
|
|
5963
6518
|
function loadDef(file: string): {
|
|
5964
6519
|
id?: string;
|
|
5965
6520
|
title?: string;
|
|
@@ -8116,6 +8671,12 @@ var init_interactive = __esm(() => {
|
|
|
8116
8671
|
function toTitle(s) {
|
|
8117
8672
|
return s.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
8118
8673
|
}
|
|
8674
|
+
function normalizeType(v) {
|
|
8675
|
+
if (v === undefined || v === null || v === "")
|
|
8676
|
+
return;
|
|
8677
|
+
const s = String(v).trim().toLowerCase();
|
|
8678
|
+
return EXTENSION_TYPES.includes(s) ? s : undefined;
|
|
8679
|
+
}
|
|
8119
8680
|
function toBool(v) {
|
|
8120
8681
|
if (v === undefined || v === null || v === "")
|
|
8121
8682
|
return;
|
|
@@ -8155,7 +8716,7 @@ Installing dependencies with ${pm}...
|
|
|
8155
8716
|
! ${pm} install failed — run it manually`);
|
|
8156
8717
|
return false;
|
|
8157
8718
|
}
|
|
8158
|
-
var import_cli_maker, path4, import_child_process, createCommand, create_default;
|
|
8719
|
+
var import_cli_maker, path4, import_child_process, EXTENSION_TYPES, createCommand, create_default;
|
|
8159
8720
|
var init_create = __esm(() => {
|
|
8160
8721
|
init_scaffold();
|
|
8161
8722
|
init_findProject();
|
|
@@ -8163,6 +8724,7 @@ var init_create = __esm(() => {
|
|
|
8163
8724
|
import_cli_maker = __toESM(require_dist(), 1);
|
|
8164
8725
|
path4 = __toESM(require("path"));
|
|
8165
8726
|
import_child_process = require("child_process");
|
|
8727
|
+
EXTENSION_TYPES = ["ui", "language", "empty"];
|
|
8166
8728
|
createCommand = {
|
|
8167
8729
|
name: "create",
|
|
8168
8730
|
description: "Scaffold a new VS Code extension project",
|
|
@@ -8171,8 +8733,9 @@ var init_create = __esm(() => {
|
|
|
8171
8733
|
{ name: "displayName", description: "Human-readable extension name", required: false, type: import_cli_maker.ParamType.Text },
|
|
8172
8734
|
{ name: "description", description: "Short description", required: false, type: import_cli_maker.ParamType.Text },
|
|
8173
8735
|
{ name: "publisher", description: "VS Code publisher id", required: false, type: import_cli_maker.ParamType.Text },
|
|
8174
|
-
{ name: "
|
|
8175
|
-
{ name: "
|
|
8736
|
+
{ name: "type", description: "Extension type: ui (React webview + RPC), language (syntax/snippets/icon), empty (bare)", required: false, type: import_cli_maker.ParamType.List, options: ["ui", "language", "empty"] },
|
|
8737
|
+
{ name: "ui", description: "UI framework (only for --type ui)", required: false, type: import_cli_maker.ParamType.List, options: ["react"] },
|
|
8738
|
+
{ name: "preset", description: "UI preset (only for --type ui): minimal = empty extension, full = panel + RPC sample", required: false, type: import_cli_maker.ParamType.List, options: ["minimal", "full"] },
|
|
8176
8739
|
{ name: "dir", description: "Target directory (defaults to ./<name>)", required: false, type: import_cli_maker.ParamType.Text },
|
|
8177
8740
|
{ name: "git", description: "Initialize a git repository (skips the prompt)", required: false, type: import_cli_maker.ParamType.Boolean },
|
|
8178
8741
|
{ name: "install", description: "Install dependencies after scaffolding (skips the prompt)", required: false, type: import_cli_maker.ParamType.Boolean }
|
|
@@ -8180,6 +8743,15 @@ var init_create = __esm(() => {
|
|
|
8180
8743
|
action: async (args) => {
|
|
8181
8744
|
const name = args.name;
|
|
8182
8745
|
const simpleName = name.replace(/^@[^/]+\//, "");
|
|
8746
|
+
const interactiveTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
8747
|
+
let type = normalizeType(args.type);
|
|
8748
|
+
if (!type) {
|
|
8749
|
+
type = interactiveTty ? await select("What kind of extension?", [
|
|
8750
|
+
{ label: "UI / webview", value: "ui", hint: "React panel + typed RPC bridge" },
|
|
8751
|
+
{ label: "Language support", value: "language", hint: "syntax highlighting, config, snippets, icon" },
|
|
8752
|
+
{ label: "Empty", value: "empty", hint: "bare activate/deactivate, no UI" }
|
|
8753
|
+
]) : "ui";
|
|
8754
|
+
}
|
|
8183
8755
|
const ui = args.ui ?? "react";
|
|
8184
8756
|
const preset = args.preset ?? "full";
|
|
8185
8757
|
const targetDir = path4.resolve(process.cwd(), args.dir ?? simpleName);
|
|
@@ -8190,18 +8762,18 @@ var init_create = __esm(() => {
|
|
|
8190
8762
|
description: args.description ?? `${simpleName} VS Code extension`,
|
|
8191
8763
|
publisher: args.publisher ?? "your-publisher",
|
|
8192
8764
|
ui,
|
|
8765
|
+
type,
|
|
8193
8766
|
preset,
|
|
8194
8767
|
targetDir,
|
|
8195
8768
|
templatesRoot: findTemplatesRoot()
|
|
8196
8769
|
});
|
|
8197
8770
|
const rel = path4.relative(process.cwd(), targetDir) || ".";
|
|
8198
8771
|
console.log(`
|
|
8199
|
-
✓ Created ${name} at ${rel}
|
|
8772
|
+
✓ Created ${name} (${type}) at ${rel}
|
|
8200
8773
|
`);
|
|
8201
|
-
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
8202
8774
|
const gitFlag = toBool(args.git);
|
|
8203
8775
|
const installFlag = toBool(args.install);
|
|
8204
|
-
const wantGit = gitFlag ?? (
|
|
8776
|
+
const wantGit = gitFlag ?? (interactiveTty ? await confirm("Initialize a git repository?", true) : false);
|
|
8205
8777
|
if (wantGit) {
|
|
8206
8778
|
if (which("git"))
|
|
8207
8779
|
initGit(targetDir);
|
|
@@ -8209,7 +8781,7 @@ var init_create = __esm(() => {
|
|
|
8209
8781
|
console.warn("! git not found — skipping repository init");
|
|
8210
8782
|
}
|
|
8211
8783
|
let pm = null;
|
|
8212
|
-
const wantInstall = installFlag ?? (
|
|
8784
|
+
const wantInstall = installFlag ?? (interactiveTty ? await confirm("Install dependencies?", true) : false);
|
|
8213
8785
|
let installed = false;
|
|
8214
8786
|
if (wantInstall) {
|
|
8215
8787
|
pm = which("bun") ? "bun" : which("npm") ? "npm" : null;
|
|
@@ -8224,9 +8796,16 @@ Next steps:`);
|
|
|
8224
8796
|
console.log(` cd ${rel}`);
|
|
8225
8797
|
if (!installed)
|
|
8226
8798
|
console.log(` ${run} install`);
|
|
8227
|
-
|
|
8228
|
-
|
|
8799
|
+
if (type === "language") {
|
|
8800
|
+
console.log(` ${run} run gen # sync package.json#contributes from contributes.extra.json`);
|
|
8801
|
+
console.log(` ${run} run launch # opens Extension Development Host — open a matching file to see highlighting`);
|
|
8802
|
+
console.log(` # edit syntaxes/, snippets/, language-configuration.json to refine the language
|
|
8229
8803
|
`);
|
|
8804
|
+
} else {
|
|
8805
|
+
console.log(` ${run} run launch # builds + opens Extension Development Host`);
|
|
8806
|
+
console.log(` # or \`${run} run dev\` + F5 inside VS Code for watch mode
|
|
8807
|
+
`);
|
|
8808
|
+
}
|
|
8230
8809
|
} catch (err) {
|
|
8231
8810
|
console.error(`
|
|
8232
8811
|
✗ Failed to scaffold: ${err.message}
|
|
@@ -8813,7 +9392,7 @@ var init_add4 = __esm(() => {
|
|
|
8813
9392
|
init_config();
|
|
8814
9393
|
fs9 = __toESM(require("fs"));
|
|
8815
9394
|
path10 = __toESM(require("path"));
|
|
8816
|
-
HELPER_KINDS = ["secrets", "config", "state", "notifications", "cache"];
|
|
9395
|
+
HELPER_KINDS = ["secrets", "config", "state", "notifications", "cache", "colorize"];
|
|
8817
9396
|
});
|
|
8818
9397
|
|
|
8819
9398
|
// src/lib/db/init.ts
|
|
@@ -9650,6 +10229,9 @@ function runDoctor(opts) {
|
|
|
9650
10229
|
results.push(...checkStatusBars(root));
|
|
9651
10230
|
results.push(...checkSubpanels(root));
|
|
9652
10231
|
results.push(checkContributesSync(root, pkg));
|
|
10232
|
+
const langCheck = checkLanguageAssets(root, pkg);
|
|
10233
|
+
if (langCheck)
|
|
10234
|
+
results.push(langCheck);
|
|
9653
10235
|
results.push(checkActivationEvents(pkg));
|
|
9654
10236
|
results.push(checkMarketplaceIcon(root, pkg));
|
|
9655
10237
|
results.push(checkGenScript(root));
|
|
@@ -9912,6 +10494,50 @@ function checkContributesSync(root, pkg) {
|
|
|
9912
10494
|
details: stale
|
|
9913
10495
|
};
|
|
9914
10496
|
}
|
|
10497
|
+
function checkLanguageAssets(root, pkg) {
|
|
10498
|
+
const sources = [pkg?.contributes];
|
|
10499
|
+
const extraPath = path14.join(root, "contributes.extra.json");
|
|
10500
|
+
if (fs12.existsSync(extraPath)) {
|
|
10501
|
+
try {
|
|
10502
|
+
sources.push(JSON.parse(fs12.readFileSync(extraPath, "utf8")));
|
|
10503
|
+
} catch {
|
|
10504
|
+
return {
|
|
10505
|
+
id: "language",
|
|
10506
|
+
level: "error",
|
|
10507
|
+
message: "contributes.extra.json is not valid JSON"
|
|
10508
|
+
};
|
|
10509
|
+
}
|
|
10510
|
+
}
|
|
10511
|
+
const refs = new Set;
|
|
10512
|
+
for (const c of sources) {
|
|
10513
|
+
if (!c)
|
|
10514
|
+
continue;
|
|
10515
|
+
for (const l of c.languages ?? [])
|
|
10516
|
+
if (l.configuration)
|
|
10517
|
+
refs.add(l.configuration);
|
|
10518
|
+
for (const g of c.grammars ?? [])
|
|
10519
|
+
if (g.path)
|
|
10520
|
+
refs.add(g.path);
|
|
10521
|
+
for (const s of c.snippets ?? [])
|
|
10522
|
+
if (s.path)
|
|
10523
|
+
refs.add(s.path);
|
|
10524
|
+
for (const t of c.iconThemes ?? [])
|
|
10525
|
+
if (t.path)
|
|
10526
|
+
refs.add(t.path);
|
|
10527
|
+
}
|
|
10528
|
+
if (refs.size === 0)
|
|
10529
|
+
return null;
|
|
10530
|
+
const missing = [...refs].filter((rel) => !fs12.existsSync(path14.join(root, rel)));
|
|
10531
|
+
if (missing.length === 0) {
|
|
10532
|
+
return { id: "language", level: "ok", message: `language ${refs.size} asset(s) present` };
|
|
10533
|
+
}
|
|
10534
|
+
return {
|
|
10535
|
+
id: "language",
|
|
10536
|
+
level: "error",
|
|
10537
|
+
message: `language: ${missing.length} referenced asset(s) missing`,
|
|
10538
|
+
details: missing
|
|
10539
|
+
};
|
|
10540
|
+
}
|
|
9915
10541
|
function checkGitignore(root) {
|
|
9916
10542
|
const file = path14.join(root, ".gitignore");
|
|
9917
10543
|
const required = ["dist", "node_modules"];
|
|
@@ -12097,7 +12723,7 @@ var init_add18 = __esm(() => {
|
|
|
12097
12723
|
path33 = __toESM(require("path"));
|
|
12098
12724
|
addHelperCommand = {
|
|
12099
12725
|
name: "add",
|
|
12100
|
-
description: "Generate a typed helper (secrets, config, state, notifications) into src/helpers/",
|
|
12726
|
+
description: "Generate a typed helper (secrets, config, state, notifications, cache, colorize) into src/helpers/",
|
|
12101
12727
|
params: [
|
|
12102
12728
|
{
|
|
12103
12729
|
name: "kind",
|
|
@@ -12143,6 +12769,15 @@ var init_add18 = __esm(() => {
|
|
|
12143
12769
|
import { createCache } from '../helpers/cache';
|
|
12144
12770
|
const cache = createCache<User>({ ttlMs: 60_000, max: 200 });
|
|
12145
12771
|
const u = await cache.wrap('user:' + id, () => orm(User).findById(id));
|
|
12772
|
+
`);
|
|
12773
|
+
} else if (args.kind === "colorize") {
|
|
12774
|
+
console.log(`
|
|
12775
|
+
Usage (auto-apply scoped token colors on activate):
|
|
12776
|
+
import { applyTokenColors } from '../helpers/colorize';
|
|
12777
|
+
await applyTokenColors('source.mylang', [
|
|
12778
|
+
{ scope: 'entity.name.section.mylang', settings: { foreground: '#e6c07b', fontStyle: 'bold' } },
|
|
12779
|
+
]);
|
|
12780
|
+
// add a "<prefix>.colorize" boolean to contributes.configuration to opt out
|
|
12146
12781
|
`);
|
|
12147
12782
|
} else {
|
|
12148
12783
|
console.log("");
|
|
@@ -13389,7 +14024,7 @@ var init_cli = __esm(() => {
|
|
|
13389
14024
|
import_cli_maker21 = __toESM(require_dist(), 1);
|
|
13390
14025
|
cli = new import_cli_maker21.CLI("vsceasy", "Build VS Code extensions fast — React UI + typed RPC bridge + zero-config build.", {
|
|
13391
14026
|
interactive: true,
|
|
13392
|
-
version: "0.1.
|
|
14027
|
+
version: "0.1.10",
|
|
13393
14028
|
introAnimation: {
|
|
13394
14029
|
enabled: true,
|
|
13395
14030
|
preset: "retro-space",
|