@intentius/chant 0.0.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 +365 -0
- package/package.json +22 -0
- package/src/attrref.test.ts +148 -0
- package/src/attrref.ts +50 -0
- package/src/barrel.test.ts +157 -0
- package/src/barrel.ts +101 -0
- package/src/bench.test.ts +227 -0
- package/src/build.test.ts +437 -0
- package/src/build.ts +425 -0
- package/src/builder.test.ts +312 -0
- package/src/builder.ts +56 -0
- package/src/child-project.ts +44 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
- package/src/cli/commands/build.test.ts +149 -0
- package/src/cli/commands/build.ts +344 -0
- package/src/cli/commands/diff.test.ts +148 -0
- package/src/cli/commands/diff.ts +221 -0
- package/src/cli/commands/doctor.test.ts +239 -0
- package/src/cli/commands/doctor.ts +224 -0
- package/src/cli/commands/import.test.ts +379 -0
- package/src/cli/commands/import.ts +335 -0
- package/src/cli/commands/init-lexicon.test.ts +297 -0
- package/src/cli/commands/init-lexicon.ts +993 -0
- package/src/cli/commands/init.test.ts +317 -0
- package/src/cli/commands/init.ts +505 -0
- package/src/cli/commands/licenses.ts +165 -0
- package/src/cli/commands/lint.test.ts +332 -0
- package/src/cli/commands/lint.ts +408 -0
- package/src/cli/commands/list.test.ts +100 -0
- package/src/cli/commands/list.ts +108 -0
- package/src/cli/commands/update.test.ts +38 -0
- package/src/cli/commands/update.ts +207 -0
- package/src/cli/conflict-check.test.ts +255 -0
- package/src/cli/conflict-check.ts +89 -0
- package/src/cli/debug.ts +8 -0
- package/src/cli/format.test.ts +140 -0
- package/src/cli/format.ts +133 -0
- package/src/cli/handlers/build.ts +58 -0
- package/src/cli/handlers/dev.ts +38 -0
- package/src/cli/handlers/init.ts +46 -0
- package/src/cli/handlers/lint.ts +36 -0
- package/src/cli/handlers/misc.ts +57 -0
- package/src/cli/handlers/serve.ts +26 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/lsp/capabilities.ts +46 -0
- package/src/cli/lsp/diagnostics.ts +52 -0
- package/src/cli/lsp/server.test.ts +618 -0
- package/src/cli/lsp/server.ts +393 -0
- package/src/cli/main.test.ts +257 -0
- package/src/cli/main.ts +224 -0
- package/src/cli/mcp/resources/context.ts +59 -0
- package/src/cli/mcp/server.test.ts +747 -0
- package/src/cli/mcp/server.ts +402 -0
- package/src/cli/mcp/tools/build.ts +117 -0
- package/src/cli/mcp/tools/import.ts +48 -0
- package/src/cli/mcp/tools/lint.ts +45 -0
- package/src/cli/plugins.test.ts +31 -0
- package/src/cli/plugins.ts +94 -0
- package/src/cli/registry.ts +73 -0
- package/src/cli/reporters/stylish.test.ts +282 -0
- package/src/cli/reporters/stylish.ts +186 -0
- package/src/cli/watch.test.ts +81 -0
- package/src/cli/watch.ts +101 -0
- package/src/codegen/case.test.ts +30 -0
- package/src/codegen/case.ts +11 -0
- package/src/codegen/coverage.ts +167 -0
- package/src/codegen/docs.ts +634 -0
- package/src/codegen/fetch.test.ts +119 -0
- package/src/codegen/fetch.ts +261 -0
- package/src/codegen/generate-registry.test.ts +118 -0
- package/src/codegen/generate-registry.ts +107 -0
- package/src/codegen/generate-runtime-index.test.ts +81 -0
- package/src/codegen/generate-runtime-index.ts +99 -0
- package/src/codegen/generate-typescript.test.ts +146 -0
- package/src/codegen/generate-typescript.ts +161 -0
- package/src/codegen/generate.ts +206 -0
- package/src/codegen/json-patch.test.ts +113 -0
- package/src/codegen/json-patch.ts +151 -0
- package/src/codegen/json-schema.test.ts +196 -0
- package/src/codegen/json-schema.ts +209 -0
- package/src/codegen/naming.ts +201 -0
- package/src/codegen/package.ts +161 -0
- package/src/codegen/rollback.test.ts +92 -0
- package/src/codegen/rollback.ts +115 -0
- package/src/codegen/topo-sort.test.ts +69 -0
- package/src/codegen/topo-sort.ts +46 -0
- package/src/codegen/typecheck.test.ts +37 -0
- package/src/codegen/typecheck.ts +74 -0
- package/src/codegen/validate.test.ts +86 -0
- package/src/codegen/validate.ts +143 -0
- package/src/composite.test.ts +426 -0
- package/src/composite.ts +243 -0
- package/src/config.test.ts +91 -0
- package/src/config.ts +87 -0
- package/src/declarable.test.ts +160 -0
- package/src/declarable.ts +47 -0
- package/src/detectLexicon.test.ts +236 -0
- package/src/detectLexicon.ts +37 -0
- package/src/discovery/cache.test.ts +78 -0
- package/src/discovery/cache.ts +86 -0
- package/src/discovery/collect.test.ts +269 -0
- package/src/discovery/collect.ts +51 -0
- package/src/discovery/cycles.test.ts +238 -0
- package/src/discovery/cycles.ts +107 -0
- package/src/discovery/files.test.ts +154 -0
- package/src/discovery/files.ts +61 -0
- package/src/discovery/graph.test.ts +476 -0
- package/src/discovery/graph.ts +150 -0
- package/src/discovery/import.test.ts +199 -0
- package/src/discovery/import.ts +20 -0
- package/src/discovery/index.test.ts +272 -0
- package/src/discovery/index.ts +132 -0
- package/src/discovery/resolve.test.ts +267 -0
- package/src/discovery/resolve.ts +54 -0
- package/src/errors.test.ts +138 -0
- package/src/errors.ts +86 -0
- package/src/import/base-parser.test.ts +67 -0
- package/src/import/base-parser.ts +48 -0
- package/src/import/generator.ts +21 -0
- package/src/import/ir-utils.test.ts +103 -0
- package/src/import/ir-utils.ts +87 -0
- package/src/import/parser.ts +41 -0
- package/src/index.ts +60 -0
- package/src/intrinsic-interpolation.test.ts +91 -0
- package/src/intrinsic-interpolation.ts +89 -0
- package/src/intrinsic.test.ts +69 -0
- package/src/intrinsic.ts +43 -0
- package/src/lexicon-integrity.test.ts +94 -0
- package/src/lexicon-integrity.ts +69 -0
- package/src/lexicon-manifest.test.ts +101 -0
- package/src/lexicon-manifest.ts +71 -0
- package/src/lexicon-output.test.ts +182 -0
- package/src/lexicon-output.ts +82 -0
- package/src/lexicon-schema.test.ts +239 -0
- package/src/lexicon-schema.ts +144 -0
- package/src/lexicon.ts +212 -0
- package/src/lint/config-overrides.test.ts +254 -0
- package/src/lint/config.test.ts +644 -0
- package/src/lint/config.ts +375 -0
- package/src/lint/declarative.test.ts +256 -0
- package/src/lint/declarative.ts +187 -0
- package/src/lint/engine.test.ts +465 -0
- package/src/lint/engine.ts +172 -0
- package/src/lint/named-checks.test.ts +37 -0
- package/src/lint/named-checks.ts +33 -0
- package/src/lint/parser.test.ts +129 -0
- package/src/lint/parser.ts +42 -0
- package/src/lint/post-synth.test.ts +113 -0
- package/src/lint/post-synth.ts +76 -0
- package/src/lint/presets/relaxed.json +19 -0
- package/src/lint/presets/strict.json +19 -0
- package/src/lint/rule-loader.test.ts +67 -0
- package/src/lint/rule-loader.ts +67 -0
- package/src/lint/rule-options.test.ts +141 -0
- package/src/lint/rule.test.ts +196 -0
- package/src/lint/rule.ts +98 -0
- package/src/lint/rules/barrel-import-style.test.ts +80 -0
- package/src/lint/rules/barrel-import-style.ts +59 -0
- package/src/lint/rules/composite-scope.ts +55 -0
- package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
- package/src/lint/rules/cor017-composite-name-match.ts +108 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
- package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
- package/src/lint/rules/declarable-naming-convention.ts +70 -0
- package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
- package/src/lint/rules/enforce-barrel-import.ts +81 -0
- package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
- package/src/lint/rules/enforce-barrel-ref.ts +75 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
- package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
- package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
- package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
- package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
- package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
- package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
- package/src/lint/rules/evl004-spread-non-const.ts +111 -0
- package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
- package/src/lint/rules/evl005-resource-block-body.ts +49 -0
- package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
- package/src/lint/rules/evl006-barrel-usage.ts +95 -0
- package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
- package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
- package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
- package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
- package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
- package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
- package/src/lint/rules/export-required.test.ts +213 -0
- package/src/lint/rules/export-required.ts +158 -0
- package/src/lint/rules/file-declarable-limit.test.ts +148 -0
- package/src/lint/rules/file-declarable-limit.ts +96 -0
- package/src/lint/rules/flat-declarations.test.ts +210 -0
- package/src/lint/rules/flat-declarations.ts +70 -0
- package/src/lint/rules/index.ts +99 -0
- package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
- package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
- package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
- package/src/lint/rules/no-redundant-type-import.ts +85 -0
- package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
- package/src/lint/rules/no-redundant-value-cast.ts +46 -0
- package/src/lint/rules/no-string-ref.test.ts +100 -0
- package/src/lint/rules/no-string-ref.ts +66 -0
- package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
- package/src/lint/rules/no-unused-declarable-import.ts +103 -0
- package/src/lint/rules/no-unused-declarable.test.ts +134 -0
- package/src/lint/rules/no-unused-declarable.ts +118 -0
- package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
- package/src/lint/rules/prefer-namespace-import.ts +63 -0
- package/src/lint/rules/single-concern-file.test.ts +156 -0
- package/src/lint/rules/single-concern-file.ts +98 -0
- package/src/lint/rules/stale-barrel-types.ts +60 -0
- package/src/lint/selectors.test.ts +113 -0
- package/src/lint/selectors.ts +188 -0
- package/src/lsp/lexicon-providers.ts +191 -0
- package/src/lsp/types.ts +79 -0
- package/src/mcp/types.ts +22 -0
- package/src/project/scan.test.ts +178 -0
- package/src/project/scan.ts +182 -0
- package/src/project/sync.test.ts +87 -0
- package/src/project/sync.ts +46 -0
- package/src/project-validation.test.ts +64 -0
- package/src/project-validation.ts +79 -0
- package/src/pseudo-parameter.test.ts +39 -0
- package/src/pseudo-parameter.ts +47 -0
- package/src/runtime.ts +68 -0
- package/src/serializer-walker.test.ts +124 -0
- package/src/serializer-walker.ts +83 -0
- package/src/serializer.ts +42 -0
- package/src/sort.test.ts +290 -0
- package/src/sort.ts +58 -0
- package/src/stack-output.ts +82 -0
- package/src/types.test.ts +307 -0
- package/src/types.ts +46 -0
- package/src/utils.test.ts +195 -0
- package/src/utils.ts +46 -0
- package/src/validation.test.ts +308 -0
- package/src/validation.ts +50 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync } from "fs";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { createInterface } from "readline";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { formatSuccess, formatWarning } from "../format";
|
|
7
|
+
import { loadPlugin } from "../plugins";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Schema for validating generated package.json — catches template bugs early.
|
|
11
|
+
*/
|
|
12
|
+
const GeneratedPackageJsonSchema = z.object({
|
|
13
|
+
name: z.string().min(1),
|
|
14
|
+
version: z.string(),
|
|
15
|
+
type: z.literal("module"),
|
|
16
|
+
scripts: z.record(z.string(), z.string()),
|
|
17
|
+
dependencies: z.record(z.string(), z.string()),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Init command options
|
|
22
|
+
*/
|
|
23
|
+
export interface InitOptions {
|
|
24
|
+
/** Target directory (defaults to cwd) */
|
|
25
|
+
path?: string;
|
|
26
|
+
/** Lexicon to use */
|
|
27
|
+
lexicon: string;
|
|
28
|
+
/** Force init even in non-empty directory */
|
|
29
|
+
force?: boolean;
|
|
30
|
+
/** Skip MCP config generation */
|
|
31
|
+
skipMcp?: boolean;
|
|
32
|
+
/** Skip interactive install prompt */
|
|
33
|
+
skipInstall?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Init command result
|
|
38
|
+
*/
|
|
39
|
+
export interface InitResult {
|
|
40
|
+
/** Whether init succeeded */
|
|
41
|
+
success: boolean;
|
|
42
|
+
/** Created files */
|
|
43
|
+
createdFiles: string[];
|
|
44
|
+
/** Warning messages */
|
|
45
|
+
warnings: string[];
|
|
46
|
+
/** Error message if failed */
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Detect the IDE environment for MCP config
|
|
52
|
+
*/
|
|
53
|
+
function detectIdeEnvironment(): "claude-code" | "cursor" | "generic" {
|
|
54
|
+
// Check for Claude Code
|
|
55
|
+
if (existsSync(join(homedir(), ".claude"))) {
|
|
56
|
+
return "claude-code";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for Cursor
|
|
60
|
+
if (existsSync(join(homedir(), ".cursor"))) {
|
|
61
|
+
return "cursor";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return "generic";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get MCP config directory based on IDE
|
|
69
|
+
*/
|
|
70
|
+
function getMcpConfigDir(ide: "claude-code" | "cursor" | "generic"): string {
|
|
71
|
+
switch (ide) {
|
|
72
|
+
case "claude-code":
|
|
73
|
+
return join(homedir(), ".claude");
|
|
74
|
+
case "cursor":
|
|
75
|
+
return join(homedir(), ".cursor");
|
|
76
|
+
default:
|
|
77
|
+
return join(homedir(), ".config", "mcp");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generate package.json content
|
|
83
|
+
*/
|
|
84
|
+
function generatePackageJson(lexicon: string): string {
|
|
85
|
+
const dependencies: Record<string, string> = {
|
|
86
|
+
"@intentius/chant": "^0.1.0",
|
|
87
|
+
[`@intentius/chant-lexicon-${lexicon}`]: "^0.1.0",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const pkg = {
|
|
91
|
+
name: "chant-project",
|
|
92
|
+
version: "0.1.0",
|
|
93
|
+
type: "module" as const,
|
|
94
|
+
scripts: {
|
|
95
|
+
build: `chant build src --lexicon ${lexicon}`,
|
|
96
|
+
lint: "chant lint src",
|
|
97
|
+
dev: `chant build src --lexicon ${lexicon} --watch`,
|
|
98
|
+
},
|
|
99
|
+
dependencies,
|
|
100
|
+
devDependencies: {
|
|
101
|
+
typescript: "^5.0.0",
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Validate generated output to catch template bugs
|
|
106
|
+
const result = GeneratedPackageJsonSchema.safeParse(pkg);
|
|
107
|
+
if (!result.success) {
|
|
108
|
+
throw new Error(`Bug: generated package.json is invalid: ${result.error.issues[0].message}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return JSON.stringify(pkg, null, 2);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate tsconfig.json content with path mappings for .chant/ types
|
|
116
|
+
*/
|
|
117
|
+
function generateTsConfig(lexicon: string): string {
|
|
118
|
+
const config = {
|
|
119
|
+
compilerOptions: {
|
|
120
|
+
target: "ES2022",
|
|
121
|
+
module: "NodeNext",
|
|
122
|
+
moduleResolution: "NodeNext",
|
|
123
|
+
strict: true,
|
|
124
|
+
esModuleInterop: true,
|
|
125
|
+
skipLibCheck: true,
|
|
126
|
+
declaration: true,
|
|
127
|
+
outDir: "./dist",
|
|
128
|
+
rootDir: "./src",
|
|
129
|
+
paths: {
|
|
130
|
+
"@intentius/chant": ["./.chant/types/core"],
|
|
131
|
+
"@intentius/chant/*": ["./.chant/types/core/*"],
|
|
132
|
+
[`@intentius/chant-lexicon-${lexicon}`]: [`./.chant/types/lexicon-${lexicon}`],
|
|
133
|
+
[`@intentius/chant-lexicon-${lexicon}/*`]: [`./.chant/types/lexicon-${lexicon}/*`],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
include: ["src"],
|
|
137
|
+
exclude: ["node_modules", "dist"],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return JSON.stringify(config, null, 2);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generate chant.config.ts content
|
|
145
|
+
*/
|
|
146
|
+
function generateChantConfig(lexicon: string): string {
|
|
147
|
+
return `import type { ChantConfig } from "@intentius/chant";
|
|
148
|
+
|
|
149
|
+
export default {
|
|
150
|
+
lexicons: ["${lexicon}"],
|
|
151
|
+
} satisfies ChantConfig;
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Generate .gitignore content
|
|
157
|
+
*/
|
|
158
|
+
function generateGitignore(): string {
|
|
159
|
+
return `dist/
|
|
160
|
+
node_modules/
|
|
161
|
+
.chant/types/
|
|
162
|
+
.chant/meta/
|
|
163
|
+
.chant/rules/
|
|
164
|
+
.chant/skills/
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Generate embedded core type definitions for .chant/types/core/
|
|
171
|
+
*/
|
|
172
|
+
function generateCoreTypeDefs(): string {
|
|
173
|
+
return `// @intentius/chant — core type definitions
|
|
174
|
+
// Run "chant update" to sync the latest types
|
|
175
|
+
|
|
176
|
+
/** Wraps a value that may be a literal or an intrinsic expression */
|
|
177
|
+
export type Value<T> = T | Intrinsic;
|
|
178
|
+
|
|
179
|
+
/** Marker interface for intrinsic functions (Ref, Sub, etc.) */
|
|
180
|
+
export interface Intrinsic {
|
|
181
|
+
toJSON(): unknown;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Serializer interface for chant specifications */
|
|
185
|
+
export interface Serializer {
|
|
186
|
+
name: string;
|
|
187
|
+
rulePrefix: string;
|
|
188
|
+
serialize(entities: Map<string, Declarable>, outputs?: LexiconOutput[]): string;
|
|
189
|
+
serializeCrossRef?(output: LexiconOutput): unknown;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Base interface for all declarable entities */
|
|
193
|
+
export interface Declarable {
|
|
194
|
+
readonly lexicon: string;
|
|
195
|
+
readonly entityType: string;
|
|
196
|
+
readonly kind?: "resource" | "property";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Cross-lexicon output reference */
|
|
200
|
+
export interface LexiconOutput {
|
|
201
|
+
readonly outputName: string;
|
|
202
|
+
readonly sourceEntity: string;
|
|
203
|
+
readonly sourceAttribute: string;
|
|
204
|
+
readonly lexicon: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Top-level project configuration */
|
|
208
|
+
export interface ChantConfig {
|
|
209
|
+
lexicons?: string[];
|
|
210
|
+
lint?: {
|
|
211
|
+
rules?: Record<string, string | [string, Record<string, unknown>]>;
|
|
212
|
+
extends?: string[];
|
|
213
|
+
plugins?: string[];
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Barrel proxy — lazy-loads all sibling exports */
|
|
218
|
+
export declare function barrel(dir: string): Record<string, unknown>;
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Generate MCP config
|
|
224
|
+
*/
|
|
225
|
+
function generateMcpConfig(_ide: "claude-code" | "cursor" | "generic"): string {
|
|
226
|
+
const config = {
|
|
227
|
+
mcpServers: {
|
|
228
|
+
chant: {
|
|
229
|
+
command: "npx",
|
|
230
|
+
args: ["chant", "serve", "mcp"],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
return JSON.stringify(config, null, 2);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Prompt user for install
|
|
240
|
+
*/
|
|
241
|
+
async function promptInstall(): Promise<boolean> {
|
|
242
|
+
const rl = createInterface({
|
|
243
|
+
input: process.stdin,
|
|
244
|
+
output: process.stdout,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return new Promise((resolve) => {
|
|
248
|
+
rl.question("Install dependencies? (Y/n) ", (answer) => {
|
|
249
|
+
rl.close();
|
|
250
|
+
const trimmed = answer.trim().toLowerCase();
|
|
251
|
+
resolve(trimmed === "" || trimmed === "y" || trimmed === "yes");
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Write a file if it doesn't exist, tracking created files and warnings
|
|
258
|
+
*/
|
|
259
|
+
function writeIfNotExists(
|
|
260
|
+
filePath: string,
|
|
261
|
+
content: string,
|
|
262
|
+
relativePath: string,
|
|
263
|
+
createdFiles: string[],
|
|
264
|
+
warnings: string[],
|
|
265
|
+
): void {
|
|
266
|
+
if (!existsSync(filePath)) {
|
|
267
|
+
writeFileSync(filePath, content);
|
|
268
|
+
createdFiles.push(relativePath);
|
|
269
|
+
} else {
|
|
270
|
+
warnings.push(`${relativePath} already exists, skipping`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Execute the init command
|
|
276
|
+
*/
|
|
277
|
+
export async function initCommand(options: InitOptions): Promise<InitResult> {
|
|
278
|
+
const targetDir = resolve(options.path ?? ".");
|
|
279
|
+
const createdFiles: string[] = [];
|
|
280
|
+
const warnings: string[] = [];
|
|
281
|
+
|
|
282
|
+
// Check if directory is non-empty
|
|
283
|
+
if (existsSync(targetDir)) {
|
|
284
|
+
const contents = readdirSync(targetDir);
|
|
285
|
+
const nonHiddenFiles = contents.filter((f) => !f.startsWith("."));
|
|
286
|
+
|
|
287
|
+
if (nonHiddenFiles.length > 0 && !options.force) {
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
createdFiles: [],
|
|
291
|
+
warnings: [],
|
|
292
|
+
error: `Directory is not empty. Use --force to initialize anyway.`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (nonHiddenFiles.length > 0) {
|
|
297
|
+
warnings.push("Initializing in non-empty directory");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Create target directory if it doesn't exist
|
|
302
|
+
if (!existsSync(targetDir)) {
|
|
303
|
+
mkdirSync(targetDir, { recursive: true });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Create src directory
|
|
307
|
+
const srcDir = join(targetDir, "src");
|
|
308
|
+
if (!existsSync(srcDir)) {
|
|
309
|
+
mkdirSync(srcDir, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Generate package.json
|
|
313
|
+
writeIfNotExists(
|
|
314
|
+
join(targetDir, "package.json"),
|
|
315
|
+
generatePackageJson(options.lexicon),
|
|
316
|
+
"package.json",
|
|
317
|
+
createdFiles,
|
|
318
|
+
warnings,
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// Generate tsconfig.json
|
|
322
|
+
writeIfNotExists(
|
|
323
|
+
join(targetDir, "tsconfig.json"),
|
|
324
|
+
generateTsConfig(options.lexicon),
|
|
325
|
+
"tsconfig.json",
|
|
326
|
+
createdFiles,
|
|
327
|
+
warnings,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Generate chant.config.ts
|
|
331
|
+
writeIfNotExists(
|
|
332
|
+
join(targetDir, "chant.config.ts"),
|
|
333
|
+
generateChantConfig(options.lexicon),
|
|
334
|
+
"chant.config.ts",
|
|
335
|
+
createdFiles,
|
|
336
|
+
warnings,
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// Generate .gitignore
|
|
340
|
+
writeIfNotExists(
|
|
341
|
+
join(targetDir, ".gitignore"),
|
|
342
|
+
generateGitignore(),
|
|
343
|
+
".gitignore",
|
|
344
|
+
createdFiles,
|
|
345
|
+
warnings,
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// Generate source files from plugin (or fallback to a minimal barrel)
|
|
349
|
+
let sourceFiles: Record<string, string> = {};
|
|
350
|
+
try {
|
|
351
|
+
const plugin = await loadPlugin(options.lexicon);
|
|
352
|
+
if (plugin.initTemplates) {
|
|
353
|
+
sourceFiles = plugin.initTemplates();
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
// Plugin not yet installed — write a minimal barrel stub
|
|
357
|
+
sourceFiles = {
|
|
358
|
+
"_.ts": "// Barrel — re-export shared config here\n",
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
for (const [filename, content] of Object.entries(sourceFiles)) {
|
|
362
|
+
writeIfNotExists(
|
|
363
|
+
join(srcDir, filename),
|
|
364
|
+
content,
|
|
365
|
+
`src/${filename}`,
|
|
366
|
+
createdFiles,
|
|
367
|
+
warnings,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Scaffold .chant/types/core/ with embedded type definitions
|
|
372
|
+
const coreTypesDir = join(targetDir, ".chant", "types", "core");
|
|
373
|
+
mkdirSync(coreTypesDir, { recursive: true });
|
|
374
|
+
|
|
375
|
+
writeIfNotExists(
|
|
376
|
+
join(coreTypesDir, "package.json"),
|
|
377
|
+
JSON.stringify({ name: "@intentius/chant", version: "0.0.0", types: "./index.d.ts" }, null, 2),
|
|
378
|
+
".chant/types/core/package.json",
|
|
379
|
+
createdFiles,
|
|
380
|
+
warnings,
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
writeIfNotExists(
|
|
384
|
+
join(coreTypesDir, "index.d.ts"),
|
|
385
|
+
generateCoreTypeDefs(),
|
|
386
|
+
".chant/types/core/index.d.ts",
|
|
387
|
+
createdFiles,
|
|
388
|
+
warnings,
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
// Scaffold .chant/types/lexicon-{lexicon}/ stub
|
|
392
|
+
const lexiconTypesDir = join(targetDir, ".chant", "types", `lexicon-${options.lexicon}`);
|
|
393
|
+
mkdirSync(lexiconTypesDir, { recursive: true });
|
|
394
|
+
|
|
395
|
+
writeIfNotExists(
|
|
396
|
+
join(lexiconTypesDir, "package.json"),
|
|
397
|
+
JSON.stringify(
|
|
398
|
+
{ name: `@intentius/chant-lexicon-${options.lexicon}`, version: "0.0.0", types: "./index.d.ts" },
|
|
399
|
+
null,
|
|
400
|
+
2,
|
|
401
|
+
),
|
|
402
|
+
`.chant/types/lexicon-${options.lexicon}/package.json`,
|
|
403
|
+
createdFiles,
|
|
404
|
+
warnings,
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
writeIfNotExists(
|
|
408
|
+
join(lexiconTypesDir, "index.d.ts"),
|
|
409
|
+
`// Lexicon type stubs — run "chant update" to sync full types\nexport {};\n`,
|
|
410
|
+
`.chant/types/lexicon-${options.lexicon}/index.d.ts`,
|
|
411
|
+
createdFiles,
|
|
412
|
+
warnings,
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
// Generate MCP config
|
|
416
|
+
if (!options.skipMcp) {
|
|
417
|
+
const ide = detectIdeEnvironment();
|
|
418
|
+
const mcpDir = getMcpConfigDir(ide);
|
|
419
|
+
|
|
420
|
+
if (!existsSync(mcpDir)) {
|
|
421
|
+
mkdirSync(mcpDir, { recursive: true });
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
writeIfNotExists(
|
|
425
|
+
join(mcpDir, "mcp.json"),
|
|
426
|
+
generateMcpConfig(ide),
|
|
427
|
+
`~/.${ide === "generic" ? "config/mcp" : ide}/mcp.json`,
|
|
428
|
+
createdFiles,
|
|
429
|
+
warnings,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Install skills from the lexicon's plugin
|
|
434
|
+
try {
|
|
435
|
+
const plugin = await loadPlugin(options.lexicon);
|
|
436
|
+
if (plugin.skills) {
|
|
437
|
+
const skills = plugin.skills();
|
|
438
|
+
if (skills.length > 0) {
|
|
439
|
+
const skillsDir = join(targetDir, ".chant", "skills", options.lexicon);
|
|
440
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
441
|
+
for (const skill of skills) {
|
|
442
|
+
const skillPath = join(skillsDir, `${skill.name}.md`);
|
|
443
|
+
writeFileSync(skillPath, skill.content);
|
|
444
|
+
createdFiles.push(`.chant/skills/${options.lexicon}/${skill.name}.md`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
// Skills are optional — don't fail init if plugin isn't installed yet
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
success: true,
|
|
454
|
+
createdFiles,
|
|
455
|
+
warnings,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Print init result and prompt for install
|
|
461
|
+
*/
|
|
462
|
+
export async function printInitResult(
|
|
463
|
+
result: InitResult,
|
|
464
|
+
options?: { skipInstall?: boolean; cwd?: string },
|
|
465
|
+
): Promise<void> {
|
|
466
|
+
if (!result.success) {
|
|
467
|
+
console.error(result.error);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
for (const warning of result.warnings) {
|
|
472
|
+
console.error(formatWarning({ message: warning }));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (result.createdFiles.length > 0) {
|
|
476
|
+
console.log(formatSuccess("Created:"));
|
|
477
|
+
for (const file of result.createdFiles) {
|
|
478
|
+
console.log(` ${file}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
console.log("");
|
|
483
|
+
|
|
484
|
+
// Interactive install prompt
|
|
485
|
+
if (!options?.skipInstall) {
|
|
486
|
+
const shouldInstall = await promptInstall();
|
|
487
|
+
if (shouldInstall) {
|
|
488
|
+
const { execSync } = await import("child_process");
|
|
489
|
+
const cwd = options?.cwd ?? ".";
|
|
490
|
+
console.log("Installing dependencies...");
|
|
491
|
+
try {
|
|
492
|
+
execSync("npm install", { cwd, stdio: "inherit" });
|
|
493
|
+
} catch {
|
|
494
|
+
console.error(formatWarning({ message: "Install failed. Run 'npm install' manually." }));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
console.log("");
|
|
500
|
+
console.log("Next steps:");
|
|
501
|
+
console.log(" 1. Edit src/config.ts");
|
|
502
|
+
console.log(" 2. Add resources in src/");
|
|
503
|
+
console.log(" 3. npm run build");
|
|
504
|
+
}
|
|
505
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { resolve, join } from "node:path";
|
|
2
|
+
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
export interface LicenseEntry {
|
|
5
|
+
name: string;
|
|
6
|
+
version: string;
|
|
7
|
+
license: string;
|
|
8
|
+
licenseText?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface LicensesResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
entries: LicenseEntry[];
|
|
14
|
+
output: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Read license info from a package's directory
|
|
19
|
+
*/
|
|
20
|
+
function readPackageLicense(pkgDir: string): LicenseEntry | null {
|
|
21
|
+
const pkgJsonPath = join(pkgDir, "package.json");
|
|
22
|
+
if (!existsSync(pkgJsonPath)) return null;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
26
|
+
if (!pkgJson.name) return null;
|
|
27
|
+
|
|
28
|
+
const entry: LicenseEntry = {
|
|
29
|
+
name: pkgJson.name,
|
|
30
|
+
version: pkgJson.version || "unknown",
|
|
31
|
+
license: pkgJson.license || "UNKNOWN",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Try to read LICENSE file
|
|
35
|
+
const licenseNames = ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "LICENCE.md"];
|
|
36
|
+
for (const name of licenseNames) {
|
|
37
|
+
const licensePath = join(pkgDir, name);
|
|
38
|
+
if (existsSync(licensePath)) {
|
|
39
|
+
entry.licenseText = readFileSync(licensePath, "utf-8");
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return entry;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Scan node_modules for packages
|
|
52
|
+
*/
|
|
53
|
+
function scanNodeModules(nodeModulesDir: string): LicenseEntry[] {
|
|
54
|
+
if (!existsSync(nodeModulesDir)) return [];
|
|
55
|
+
|
|
56
|
+
const entries: LicenseEntry[] = [];
|
|
57
|
+
const dirs = readdirSync(nodeModulesDir);
|
|
58
|
+
|
|
59
|
+
for (const dir of dirs) {
|
|
60
|
+
if (dir.startsWith(".")) continue;
|
|
61
|
+
|
|
62
|
+
const fullPath = join(nodeModulesDir, dir);
|
|
63
|
+
if (!statSync(fullPath).isDirectory()) continue;
|
|
64
|
+
|
|
65
|
+
if (dir.startsWith("@")) {
|
|
66
|
+
// Scoped packages
|
|
67
|
+
const scopedDirs = readdirSync(fullPath);
|
|
68
|
+
for (const scopedDir of scopedDirs) {
|
|
69
|
+
const scopedPath = join(fullPath, scopedDir);
|
|
70
|
+
if (statSync(scopedPath).isDirectory()) {
|
|
71
|
+
const entry = readPackageLicense(scopedPath);
|
|
72
|
+
if (entry) entries.push(entry);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
const entry = readPackageLicense(fullPath);
|
|
77
|
+
if (entry) entries.push(entry);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Format license entries as a text table
|
|
86
|
+
*/
|
|
87
|
+
function formatText(entries: LicenseEntry[]): string {
|
|
88
|
+
if (entries.length === 0) return "No third-party packages found.";
|
|
89
|
+
|
|
90
|
+
// Group by license type
|
|
91
|
+
const groups = new Map<string, LicenseEntry[]>();
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
const existing = groups.get(entry.license) ?? [];
|
|
94
|
+
existing.push(entry);
|
|
95
|
+
groups.set(entry.license, existing);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const lines: string[] = [];
|
|
99
|
+
lines.push(`Third-party licenses (${entries.length} packages):`);
|
|
100
|
+
lines.push("");
|
|
101
|
+
|
|
102
|
+
// Find max widths for alignment
|
|
103
|
+
const maxName = Math.max(...entries.map((e) => e.name.length), 7);
|
|
104
|
+
const maxVersion = Math.max(...entries.map((e) => e.version.length), 7);
|
|
105
|
+
|
|
106
|
+
lines.push(
|
|
107
|
+
`${"Package".padEnd(maxName)} ${"Version".padEnd(maxVersion)} License`,
|
|
108
|
+
);
|
|
109
|
+
lines.push(`${"─".repeat(maxName)} ${"─".repeat(maxVersion)} ${"─".repeat(15)}`);
|
|
110
|
+
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
lines.push(
|
|
113
|
+
`${entry.name.padEnd(maxName)} ${entry.version.padEnd(maxVersion)} ${entry.license}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
lines.push("");
|
|
118
|
+
lines.push("License summary:");
|
|
119
|
+
const sortedGroups = [...groups.entries()].sort((a, b) => b[1].length - a[1].length);
|
|
120
|
+
for (const [license, pkgs] of sortedGroups) {
|
|
121
|
+
lines.push(` ${license}: ${pkgs.length}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Format license entries as JSON
|
|
129
|
+
*/
|
|
130
|
+
function formatJson(entries: LicenseEntry[]): string {
|
|
131
|
+
return JSON.stringify(
|
|
132
|
+
entries.map(({ name, version, license }) => ({ name, version, license })),
|
|
133
|
+
null,
|
|
134
|
+
2,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Execute the licenses command
|
|
140
|
+
*/
|
|
141
|
+
export async function licensesCommand(options: {
|
|
142
|
+
path: string;
|
|
143
|
+
format: "text" | "json";
|
|
144
|
+
}): Promise<LicensesResult> {
|
|
145
|
+
const projectPath = resolve(options.path);
|
|
146
|
+
const nodeModulesDir = join(projectPath, "node_modules");
|
|
147
|
+
|
|
148
|
+
const entries = scanNodeModules(nodeModulesDir);
|
|
149
|
+
|
|
150
|
+
const output =
|
|
151
|
+
options.format === "json" ? formatJson(entries) : formatText(entries);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
entries,
|
|
156
|
+
output,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Print licenses result to console
|
|
162
|
+
*/
|
|
163
|
+
export function printLicensesResult(result: LicensesResult): void {
|
|
164
|
+
console.log(result.output);
|
|
165
|
+
}
|