@massu/core 1.5.0 → 1.5.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/dist/cli.js CHANGED
@@ -3082,6 +3082,167 @@ var init_dist = __esm({
3082
3082
  }
3083
3083
  });
3084
3084
 
3085
+ // src/detect/manifest-registry.ts
3086
+ var manifest_registry_exports = {};
3087
+ __export(manifest_registry_exports, {
3088
+ getManifestPatterns: () => getManifestPatterns,
3089
+ getManifestRegistry: () => getManifestRegistry,
3090
+ matchManifestPattern: () => matchManifestPattern
3091
+ });
3092
+ function matchManifestPattern(name2, pattern) {
3093
+ if (pattern.startsWith("*")) {
3094
+ const suffix = pattern.slice(1);
3095
+ if (suffix.includes("*")) {
3096
+ throw new Error(
3097
+ `[manifest-registry] pattern "${pattern}" has more than one wildcard. Only "*.<ext>" extension-globs are supported.`
3098
+ );
3099
+ }
3100
+ return name2.endsWith(suffix);
3101
+ }
3102
+ return name2 === pattern;
3103
+ }
3104
+ function getManifestRegistry() {
3105
+ if (_registryCache !== null) return _registryCache;
3106
+ _registryCache = [
3107
+ {
3108
+ pattern: "package.json",
3109
+ manifestType: "package.json",
3110
+ language: "typescript",
3111
+ runtime: "node",
3112
+ parse: parsePackageJson,
3113
+ signalKey: "packageJson",
3114
+ signalShape: "json"
3115
+ },
3116
+ {
3117
+ pattern: "pyproject.toml",
3118
+ manifestType: "pyproject.toml",
3119
+ language: "python",
3120
+ runtime: "python3",
3121
+ parse: parsePyproject,
3122
+ signalKey: "pyprojectToml",
3123
+ signalShape: "toml"
3124
+ },
3125
+ {
3126
+ pattern: "requirements.txt",
3127
+ manifestType: "requirements.txt",
3128
+ language: "python",
3129
+ runtime: "python3",
3130
+ parse: parseRequirementsTxt,
3131
+ // Captured via pyprojectToml sibling already; no separate signal.
3132
+ signalKey: null,
3133
+ signalShape: "string"
3134
+ },
3135
+ {
3136
+ pattern: "Pipfile",
3137
+ manifestType: "Pipfile",
3138
+ language: "python",
3139
+ runtime: "python3",
3140
+ parse: parsePipfile,
3141
+ // Captured via pyprojectToml sibling already; no separate signal.
3142
+ signalKey: null,
3143
+ signalShape: "string"
3144
+ },
3145
+ {
3146
+ pattern: "Cargo.toml",
3147
+ manifestType: "Cargo.toml",
3148
+ language: "rust",
3149
+ runtime: "cargo",
3150
+ parse: parseCargoToml,
3151
+ signalKey: "cargoToml",
3152
+ signalShape: "toml"
3153
+ },
3154
+ {
3155
+ pattern: "Package.swift",
3156
+ manifestType: "Package.swift",
3157
+ language: "swift",
3158
+ runtime: "xcode",
3159
+ parse: parsePackageSwift,
3160
+ // No AST adapter consumer yet (swift-swiftui doesn't need it).
3161
+ signalKey: null,
3162
+ signalShape: "string"
3163
+ },
3164
+ {
3165
+ pattern: "go.mod",
3166
+ manifestType: "go.mod",
3167
+ language: "go",
3168
+ runtime: "go",
3169
+ parse: parseGoMod,
3170
+ signalKey: "goMod",
3171
+ signalShape: "string"
3172
+ },
3173
+ {
3174
+ pattern: "pom.xml",
3175
+ manifestType: "pom.xml",
3176
+ language: "java",
3177
+ runtime: "jvm",
3178
+ parse: parsePomXml,
3179
+ signalKey: "pomXml",
3180
+ signalShape: "string"
3181
+ },
3182
+ {
3183
+ pattern: "build.gradle",
3184
+ manifestType: "build.gradle",
3185
+ language: "java",
3186
+ runtime: "jvm",
3187
+ parse: parseBuildGradle,
3188
+ signalKey: "gradleBuild",
3189
+ signalShape: "string"
3190
+ },
3191
+ {
3192
+ pattern: "build.gradle.kts",
3193
+ manifestType: "build.gradle",
3194
+ language: "java",
3195
+ runtime: "jvm",
3196
+ parse: parseBuildGradle,
3197
+ signalKey: "gradleBuild",
3198
+ signalShape: "string"
3199
+ },
3200
+ {
3201
+ pattern: "Gemfile",
3202
+ manifestType: "Gemfile",
3203
+ language: "ruby",
3204
+ runtime: "ruby",
3205
+ parse: parseGemfile,
3206
+ signalKey: "gemfile",
3207
+ signalShape: "string"
3208
+ },
3209
+ // Plan 1.5.1 — closes CR-39 violation (1.5.0 init failed for Phoenix
3210
+ // + ASP.NET fixtures). Both rely on AST adapters that already work
3211
+ // in introspect; the gap was solely package-detector unaware of the
3212
+ // manifest filenames.
3213
+ {
3214
+ pattern: "mix.exs",
3215
+ manifestType: "mix.exs",
3216
+ language: "elixir",
3217
+ runtime: "beam",
3218
+ parse: parseMixExs,
3219
+ signalKey: "mixExs",
3220
+ signalShape: "string"
3221
+ },
3222
+ {
3223
+ pattern: "*.csproj",
3224
+ manifestType: "*.csproj",
3225
+ language: "csharp",
3226
+ runtime: "dotnet",
3227
+ parse: parseCsproj,
3228
+ signalKey: "csproj",
3229
+ signalShape: "string"
3230
+ }
3231
+ ];
3232
+ return _registryCache;
3233
+ }
3234
+ function getManifestPatterns() {
3235
+ return getManifestRegistry().map((e2) => e2.pattern);
3236
+ }
3237
+ var _registryCache;
3238
+ var init_manifest_registry = __esm({
3239
+ "src/detect/manifest-registry.ts"() {
3240
+ "use strict";
3241
+ init_package_detector();
3242
+ _registryCache = null;
3243
+ }
3244
+ });
3245
+
3085
3246
  // src/detect/package-detector.ts
3086
3247
  import { readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync2, lstatSync, readdirSync as readdirSync3 } from "fs";
3087
3248
  import { join as join3, relative as relative2 } from "path";
@@ -3489,46 +3650,86 @@ function parseGemfile(path, directory, root, _warnings) {
3489
3650
  manifestType: "Gemfile"
3490
3651
  };
3491
3652
  }
3653
+ function parseMixExs(path, directory, root, _warnings) {
3654
+ const raw = safeRead(path);
3655
+ if (raw === null) return null;
3656
+ const deps = [];
3657
+ const depPattern = /\{\s*:([a-z][a-z0-9_]*)\s*,/g;
3658
+ let m3;
3659
+ while ((m3 = depPattern.exec(raw)) !== null) {
3660
+ if (!deps.includes(m3[1])) deps.push(m3[1]);
3661
+ }
3662
+ const appMatch = /\bapp\s*:\s*:([a-z][a-z0-9_]*)/.exec(raw);
3663
+ const name2 = appMatch ? appMatch[1] : null;
3664
+ return {
3665
+ path,
3666
+ relativePath: normalizeRelative(root, path),
3667
+ directory,
3668
+ language: "elixir",
3669
+ runtime: "beam",
3670
+ name: name2,
3671
+ version: null,
3672
+ dependencies: deps,
3673
+ devDependencies: [],
3674
+ scripts: [],
3675
+ manifestType: "mix.exs"
3676
+ };
3677
+ }
3678
+ function parseCsproj(path, directory, root, _warnings) {
3679
+ const raw = safeRead(path);
3680
+ if (raw === null) return null;
3681
+ const deps = [];
3682
+ const pkgRefPattern = /<PackageReference\s+[^>]*Include\s*=\s*"([^"]+)"/gi;
3683
+ let m3;
3684
+ while ((m3 = pkgRefPattern.exec(raw)) !== null) {
3685
+ if (!deps.includes(m3[1])) deps.push(m3[1]);
3686
+ }
3687
+ const sdkMatch = /<Project\s+[^>]*Sdk\s*=\s*"([^"]+)"/i.exec(raw);
3688
+ if (sdkMatch && !deps.includes(sdkMatch[1])) {
3689
+ deps.push(sdkMatch[1]);
3690
+ }
3691
+ const fname = path.split(/[/\\]/).pop() ?? "";
3692
+ const name2 = fname.endsWith(".csproj") ? fname.slice(0, -".csproj".length) : null;
3693
+ return {
3694
+ path,
3695
+ relativePath: normalizeRelative(root, path),
3696
+ directory,
3697
+ language: "csharp",
3698
+ runtime: "dotnet",
3699
+ name: name2,
3700
+ version: null,
3701
+ dependencies: deps,
3702
+ devDependencies: [],
3703
+ scripts: [],
3704
+ manifestType: "*.csproj"
3705
+ };
3706
+ }
3492
3707
  function detectManifestsInDir(dir, root, warnings) {
3708
+ const { getManifestRegistry: getManifestRegistry2, matchManifestPattern: matchManifestPattern2 } = manifest_registry_exports;
3493
3709
  const out2 = [];
3494
- for (const fname of MANIFEST_FILES) {
3495
- const path = join3(dir, fname);
3496
- if (!existsSync5(path)) continue;
3497
- let m3 = null;
3498
- switch (fname) {
3499
- case "package.json":
3500
- m3 = parsePackageJson(path, dir, root, warnings);
3501
- break;
3502
- case "pyproject.toml":
3503
- m3 = parsePyproject(path, dir, root, warnings);
3504
- break;
3505
- case "requirements.txt":
3506
- m3 = parseRequirementsTxt(path, dir, root, warnings);
3507
- break;
3508
- case "Pipfile":
3509
- m3 = parsePipfile(path, dir, root, warnings);
3510
- break;
3511
- case "Cargo.toml":
3512
- m3 = parseCargoToml(path, dir, root, warnings);
3513
- break;
3514
- case "Package.swift":
3515
- m3 = parsePackageSwift(path, dir, root, warnings);
3516
- break;
3517
- case "go.mod":
3518
- m3 = parseGoMod(path, dir, root, warnings);
3519
- break;
3520
- case "pom.xml":
3521
- m3 = parsePomXml(path, dir, root, warnings);
3522
- break;
3523
- case "build.gradle":
3524
- case "build.gradle.kts":
3525
- m3 = parseBuildGradle(path, dir, root, warnings);
3526
- break;
3527
- case "Gemfile":
3528
- m3 = parseGemfile(path, dir, root, warnings);
3529
- break;
3710
+ let dirEntries = null;
3711
+ for (const entry of getManifestRegistry2()) {
3712
+ if (!entry.pattern.startsWith("*")) {
3713
+ const path = join3(dir, entry.pattern);
3714
+ if (!existsSync5(path)) continue;
3715
+ const m3 = entry.parse(path, dir, root, warnings);
3716
+ if (m3 !== null) out2.push(m3);
3717
+ } else {
3718
+ if (dirEntries === null) {
3719
+ try {
3720
+ dirEntries = readdirSync3(dir);
3721
+ } catch {
3722
+ dirEntries = [];
3723
+ }
3724
+ }
3725
+ for (const fname of dirEntries) {
3726
+ if (!matchManifestPattern2(fname, entry.pattern)) continue;
3727
+ const path = join3(dir, fname);
3728
+ if (!existsSync5(path)) continue;
3729
+ const m3 = entry.parse(path, dir, root, warnings);
3730
+ if (m3 !== null) out2.push(m3);
3731
+ }
3530
3732
  }
3531
- if (m3 !== null) out2.push(m3);
3532
3733
  }
3533
3734
  return out2;
3534
3735
  }
@@ -3562,11 +3763,12 @@ function detectPackageManifests(projectRoot) {
3562
3763
  }
3563
3764
  return { manifests: dedup, warnings };
3564
3765
  }
3565
- var WORKSPACE_DIRS, IGNORED_DIRS, MANIFEST_FILES;
3766
+ var WORKSPACE_DIRS, IGNORED_DIRS;
3566
3767
  var init_package_detector = __esm({
3567
3768
  "src/detect/package-detector.ts"() {
3568
3769
  "use strict";
3569
3770
  init_dist();
3771
+ init_manifest_registry();
3570
3772
  WORKSPACE_DIRS = ["apps", "packages", "services", "libs", "modules"];
3571
3773
  IGNORED_DIRS = /* @__PURE__ */ new Set([
3572
3774
  "node_modules",
@@ -3589,19 +3791,6 @@ var init_package_detector = __esm({
3589
3791
  "DerivedData",
3590
3792
  "Pods"
3591
3793
  ]);
3592
- MANIFEST_FILES = [
3593
- "package.json",
3594
- "pyproject.toml",
3595
- "requirements.txt",
3596
- "Pipfile",
3597
- "Cargo.toml",
3598
- "Package.swift",
3599
- "go.mod",
3600
- "pom.xml",
3601
- "build.gradle",
3602
- "build.gradle.kts",
3603
- "Gemfile"
3604
- ];
3605
3794
  }
3606
3795
  });
3607
3796
 
@@ -3757,6 +3946,13 @@ var init_framework_detector = __esm({
3757
3946
  { language: "go", kind: "framework", keyword: "github.com/labstack/echo", value: "echo", priority: 10 },
3758
3947
  { language: "go", kind: "framework", keyword: "github.com/gofiber/fiber", value: "fiber", priority: 10 },
3759
3948
  { language: "go", kind: "framework", keyword: "github.com/go-chi/chi", value: "chi", priority: 9 },
3949
+ // chi versioned import paths (Go convention: github.com/<org>/<name>/v<N>).
3950
+ // matchRule does exact case-insensitive set lookup, so the unversioned and
3951
+ // each major-version path each need their own rule.
3952
+ { language: "go", kind: "framework", keyword: "github.com/go-chi/chi/v2", value: "chi", priority: 9 },
3953
+ { language: "go", kind: "framework", keyword: "github.com/go-chi/chi/v3", value: "chi", priority: 9 },
3954
+ { language: "go", kind: "framework", keyword: "github.com/go-chi/chi/v4", value: "chi", priority: 9 },
3955
+ { language: "go", kind: "framework", keyword: "github.com/go-chi/chi/v5", value: "chi", priority: 9 },
3760
3956
  { language: "go", kind: "test_framework", keyword: "github.com/stretchr/testify", value: "testify", priority: 8 },
3761
3957
  { language: "go", kind: "orm", keyword: "gorm.io/gorm", value: "gorm", priority: 10 },
3762
3958
  // Swift (SPM dependency names, best-effort)
@@ -3772,7 +3968,26 @@ var init_framework_detector = __esm({
3772
3968
  { language: "ruby", kind: "framework", keyword: "rails", value: "rails", priority: 10 },
3773
3969
  { language: "ruby", kind: "framework", keyword: "sinatra", value: "sinatra", priority: 9 },
3774
3970
  { language: "ruby", kind: "test_framework", keyword: "rspec", value: "rspec", priority: 10 },
3775
- { language: "ruby", kind: "orm", keyword: "activerecord", value: "activerecord", priority: 10 }
3971
+ { language: "ruby", kind: "orm", keyword: "activerecord", value: "activerecord", priority: 10 },
3972
+ // Plan 1.5.1: elixir + csharp framework rules. Closes the CR-39 gap
3973
+ // where Phoenix + ASP.NET projects produced `framework.languages.<lang>`
3974
+ // entries WITHOUT a `framework:` value, which prevented variant
3975
+ // templates from being looked up.
3976
+ { language: "elixir", kind: "framework", keyword: "phoenix", value: "phoenix", priority: 10 },
3977
+ { language: "elixir", kind: "test_framework", keyword: "ex_unit", value: "ex-unit", priority: 10 },
3978
+ { language: "elixir", kind: "orm", keyword: "ecto", value: "ecto", priority: 10 },
3979
+ // ASP.NET Core surfaces via several PackageReference names; the canonical
3980
+ // ones in modern .NET projects are .App and .Mvc. matchRule does exact
3981
+ // (case-insensitive) lookup against the deps set parseCsproj extracts.
3982
+ { language: "csharp", kind: "framework", keyword: "Microsoft.AspNetCore.App", value: "aspnet-core", priority: 10 },
3983
+ { language: "csharp", kind: "framework", keyword: "Microsoft.AspNetCore.Mvc", value: "aspnet-core", priority: 10 },
3984
+ { language: "csharp", kind: "framework", keyword: "Microsoft.AspNetCore", value: "aspnet-core", priority: 9 },
3985
+ // SDK-style projects: `<Project Sdk="Microsoft.NET.Sdk.Web">` is the
3986
+ // canonical ASP.NET Core declaration in modern .NET. parseCsproj
3987
+ // surfaces the Sdk attribute as a dep so this rule can match.
3988
+ { language: "csharp", kind: "framework", keyword: "Microsoft.NET.Sdk.Web", value: "aspnet-core", priority: 10 },
3989
+ { language: "csharp", kind: "test_framework", keyword: "xunit", value: "xunit", priority: 10 },
3990
+ { language: "csharp", kind: "orm", keyword: "EntityFrameworkCore", value: "ef-core", priority: 10 }
3776
3991
  ];
3777
3992
  }
3778
3993
  });
@@ -9694,7 +9909,10 @@ var init_source_dir_detector = __esm({
9694
9909
  swift: ["swift"],
9695
9910
  go: ["go"],
9696
9911
  java: ["java", "kt"],
9697
- ruby: ["rb"]
9912
+ ruby: ["rb"],
9913
+ // Plan 1.5.1 — closing CR-39 init gap for Phoenix + ASP.NET projects.
9914
+ elixir: ["ex", "exs"],
9915
+ csharp: ["cs"]
9698
9916
  };
9699
9917
  TEST_FILE_PATTERNS = {
9700
9918
  python: [/_test\.py$/, /test_[^/]*\.py$/],
@@ -9704,7 +9922,11 @@ var init_source_dir_detector = __esm({
9704
9922
  swift: [/Tests\//],
9705
9923
  go: [/_test\.go$/],
9706
9924
  java: [/Test[^/]*\.(java|kt)$/, /[^/]*Test\.(java|kt)$/],
9707
- ruby: [/_spec\.rb$/, /_test\.rb$/]
9925
+ ruby: [/_spec\.rb$/, /_test\.rb$/],
9926
+ // Phoenix/ExUnit canonical: `test/**_test.exs`. ASP.NET / xUnit
9927
+ // canonical: `*Tests.cs` or `*.Tests/...`.
9928
+ elixir: [/_test\.exs$/, /\/test\//],
9929
+ csharp: [/Tests?\.cs$/, /\.Tests?\//]
9708
9930
  };
9709
9931
  TEST_DIR_KEYWORDS = ["tests", "test", "__tests__", "spec", "specs"];
9710
9932
  }
@@ -15688,6 +15910,7 @@ ${import_picocolors2.default.gray(m2)} ${s}
15688
15910
  // src/commands/init.ts
15689
15911
  var init_exports = {};
15690
15912
  __export(init_exports, {
15913
+ applyVariantTemplate: () => applyVariantTemplate,
15691
15914
  buildConfigFromDetection: () => buildConfigFromDetection,
15692
15915
  buildHooksConfig: () => buildHooksConfig,
15693
15916
  copyTemplateConfig: () => copyTemplateConfig,
@@ -16010,6 +16233,76 @@ function buildConfigFromDetection(opts) {
16010
16233
  }
16011
16234
  return config;
16012
16235
  }
16236
+ function applyVariantTemplate(config, templatesDir) {
16237
+ if (!templatesDir) return config;
16238
+ const fw = config.framework;
16239
+ if (!fw) return config;
16240
+ const langs = fw.languages;
16241
+ if (!langs || typeof langs !== "object") return config;
16242
+ let templateId = null;
16243
+ for (const langEntry of Object.values(langs)) {
16244
+ if (langEntry && typeof langEntry === "object") {
16245
+ const fwName = langEntry.framework;
16246
+ if (typeof fwName === "string" && FRAMEWORK_TO_TEMPLATE_ID[fwName]) {
16247
+ templateId = FRAMEWORK_TO_TEMPLATE_ID[fwName];
16248
+ break;
16249
+ }
16250
+ }
16251
+ }
16252
+ if (templateId === null) return config;
16253
+ const templatePath = resolve6(templatesDir, templateId, "massu.config.yaml");
16254
+ if (!existsSync9(templatePath)) return config;
16255
+ let template;
16256
+ try {
16257
+ template = yamlParse(readFileSync7(templatePath, "utf-8"));
16258
+ } catch {
16259
+ return config;
16260
+ }
16261
+ const out2 = { ...config };
16262
+ const tplFw = template.framework;
16263
+ const outFw = out2.framework ?? {};
16264
+ if (tplFw) {
16265
+ if (typeof tplFw.router === "string" && (!outFw.router || outFw.router === "none")) {
16266
+ outFw.router = tplFw.router;
16267
+ }
16268
+ if (typeof tplFw.orm === "string" && (!outFw.orm || outFw.orm === "none")) {
16269
+ outFw.orm = tplFw.orm;
16270
+ }
16271
+ if (typeof tplFw.ui === "string" && (!outFw.ui || outFw.ui === "none")) {
16272
+ outFw.ui = tplFw.ui;
16273
+ }
16274
+ }
16275
+ out2.framework = outFw;
16276
+ const tplPaths = template.paths;
16277
+ const outPaths = out2.paths ?? {};
16278
+ if (tplPaths && typeof tplPaths.source === "string" && tplPaths.source) {
16279
+ outPaths.source = tplPaths.source;
16280
+ }
16281
+ out2.paths = outPaths;
16282
+ const tplVerify = template.verification;
16283
+ const outVerify = out2.verification ?? {};
16284
+ if (tplVerify) {
16285
+ for (const [lang, tplLangVerify] of Object.entries(tplVerify)) {
16286
+ if (!tplLangVerify || typeof tplLangVerify !== "object") continue;
16287
+ const outLangVerify = outVerify[lang] ?? {};
16288
+ for (const key of ["lint", "syntax"]) {
16289
+ if (typeof tplLangVerify[key] === "string" && tplLangVerify[key]) {
16290
+ outLangVerify[key] = tplLangVerify[key];
16291
+ }
16292
+ }
16293
+ for (const key of ["test", "type", "build"]) {
16294
+ if (typeof tplLangVerify[key] === "string" && tplLangVerify[key] && !outLangVerify[key]) {
16295
+ outLangVerify[key] = tplLangVerify[key];
16296
+ }
16297
+ }
16298
+ outVerify[lang] = outLangVerify;
16299
+ }
16300
+ }
16301
+ if (Object.keys(outVerify).length > 0) {
16302
+ out2.verification = outVerify;
16303
+ }
16304
+ return out2;
16305
+ }
16013
16306
  function renderConfigYaml(config) {
16014
16307
  return `# Massu AI Configuration
16015
16308
  # Generated by: npx massu init (schema_version=2, detection-driven)
@@ -16501,7 +16794,8 @@ async function runInit(argv, overrides) {
16501
16794
  return;
16502
16795
  }
16503
16796
  }
16504
- const config = buildConfigFromDetection({ projectRoot, detection });
16797
+ const baseConfig = buildConfigFromDetection({ projectRoot, detection });
16798
+ const config = applyVariantTemplate(baseConfig, resolveTemplatesDir());
16505
16799
  const content = renderConfigYaml(config);
16506
16800
  const writeRes = writeConfigAtomic(configPath, content);
16507
16801
  if (!writeRes.validated) {
@@ -16637,7 +16931,7 @@ async function promptStackConfirm() {
16637
16931
  function capitalize(str) {
16638
16932
  return str.charAt(0).toUpperCase() + str.slice(1);
16639
16933
  }
16640
- var __filename2, __dirname2, TEMPLATE_NAMES;
16934
+ var __filename2, __dirname2, FRAMEWORK_TO_TEMPLATE_ID, TEMPLATE_NAMES;
16641
16935
  var init_init = __esm({
16642
16936
  "src/commands/init.ts"() {
16643
16937
  "use strict";
@@ -16648,6 +16942,14 @@ var init_init = __esm({
16648
16942
  init_drift();
16649
16943
  __filename2 = fileURLToPath2(import.meta.url);
16650
16944
  __dirname2 = dirname4(__filename2);
16945
+ FRAMEWORK_TO_TEMPLATE_ID = {
16946
+ rails: "rails",
16947
+ phoenix: "phoenix",
16948
+ "aspnet-core": "aspnet",
16949
+ "spring-boot": "spring",
16950
+ chi: "go-chi",
16951
+ flask: "python-flask"
16952
+ };
16651
16953
  TEMPLATE_NAMES = [
16652
16954
  "python-fastapi",
16653
16955
  "python-django",