@c6fc/spellcraft 0.1.2 → 0.1.3

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 CHANGED
@@ -2,192 +2,169 @@
2
2
 
3
3
  **The Sorcerer's Toolkit for Unified Configuration Management.**
4
4
 
5
- SpellCraft is a powerful framework for generating and managing configurations across a diverse toolchain. It was born from the challenge of orchestrating tools like Terraform, Packer, Kubernetes, and Ansible, each with its own fragmented and isolated configuration format (YAML, JSON, HCL, etc.).
5
+ SpellCraft is a plugin framework for [Jsonnet](https://jsonnet.org/) that bridges the gap between declarative configuration and the Node.js ecosystem. It allows you to import NPM packages directly into your Jsonnet logic, execute native JavaScript functions during configuration generation, and manage complex infrastructure-as-code requirements from a single, gnostic workflow.
6
6
 
7
- SpellCraft provides a single, unified source of truth, allowing you to generate tightly-integrated, context-aware configurations for any tool, all from one place.
7
+ SpellCraft provides a single, unified source of truth, letting you orchestrate any tool that needs machine-readable configurations (like Terraform, Packer, Kubernetes, or Ansible) from one place.
8
8
 
9
9
  [![NPM Version](https://img.shields.io/npm/v/@c6fc/spellcraft.svg)](https://www.npmjs.com/package/@c6fc/spellcraft)
10
- [![License](https://img.shields.io/npm/l/@c6fc/spellcraft.svg)](https://github.com/your-repo/spellcraft/blob/main/LICENSE)
10
+ [![License](https://img.shields.io/npm/l/@c6fc/spellcraft.svg)](https://github.com/c6fc/spellcraft/blob/main/LICENSE)
11
11
 
12
12
  ---
13
13
 
14
14
  ## The SpellCraft Philosophy
15
15
 
16
- SpellCraft is built on three core principles to provide a superior Infrastructure-as-Code experience:
17
-
18
- 1. **Declarative Power (Jsonnet):** Configurations are written in [Jsonnet](https://jsonnet.org/), a superset of JSON. This gives you the power of variables, functions, conditionals, loops, and inheritance, eliminating the endless copy-pasting and structural limitations of plain YAML or JSON. Define a component once, and reuse it everywhere.
19
-
20
- 2. **Seamless Extensibility (Node.js):** Sometimes, declarative logic isn't enough. SpellCraft allows you to "escape" the confines of Jsonnet by writing custom logic in Node.js. Need to fetch a secret from a vault, call a third-party API, or perform complex data manipulation? Simply write a JavaScript function and expose it directly to your Jsonnet code as a `std.native()` function.
21
-
22
- 3. **Robust Modularity (NPM):** SpellCraft's module system is built on the battle-tested foundation of NPM. This means you can version, share, and manage your infrastructure modules just like any other software dependency. Leverage public or private NPM registries to build a reusable, maintainable, and collaborative infrastructure codebase.
16
+ 1. **Declarative Power (Jsonnet):** Configurations are written in Jsonnet. Variables, functions, and inheritance allow you to define components once and reuse them everywhere.
17
+ 2. **Native Node.js Resolution:** No custom registries. No hidden magic. SpellCraft modules are just NPM packages. If you can `npm install` it, SpellCraft can load it.
18
+ 3. **Scoped Extensibility:** Native JavaScript functions are automatically namespaced based on their package name, ensuring that dependencies never clash, even if multiple modules use different versions of the same library.
23
19
 
24
20
  ## Quick Start
25
21
 
26
- Get up and running with SpellCraft in minutes.
27
-
28
22
  ### 1. Installation
29
23
 
30
- Install the SpellCraft CLI and core library into your project.
24
+ Install the CLI and core library.
31
25
 
32
26
  ```sh
33
27
  npm install --save @c6fc/spellcraft
34
28
  ```
35
29
 
36
- ### 2. Import a Module
30
+ ### 2. Install a Plugin
37
31
 
38
- Modules are the building blocks of SpellCraft. Let's import a module for interacting with AWS. The `importModule` command will install the package from NPM and link it into your project.
32
+ Install a SpellCraft-compatible plugin using standard NPM.
39
33
 
40
34
  ```sh
41
- npx spellcraft importModule @c6fc/spellcraft-aws-auth
42
-
43
- # Expected Output:
44
- # [*] Attempting to install @c6fc/spellcraft-aws-auth...
45
- # [+] Successfully installed @c6fc/spellcraft-aws-auth.
46
- # [+] Linked @c6fc/spellcraft-aws-auth as SpellCraft module 'awsauth'
35
+ npm install --save @c6fc/spellcraft-aws-auth
47
36
  ```
48
- This makes the `@c6fc/spellcraft-aws-auth` package available in your Jsonnet files under the name `awsauth`.
49
37
 
50
- ### 3. Create Your First Spell
38
+ ### 3. Write Your Spell
51
39
 
52
- A "Spell" is a `.jsonnet` file that defines the files you want to create. The top-level keys of the output object become filenames.
40
+ Create a `manifest.jsonnet` file. Unlike previous versions of SpellCraft, you import modules explicitly using standard Node resolution.
53
41
 
54
- Create a file named `manifest.jsonnet`:
55
42
  ```jsonnet
56
- // manifest.jsonnet
57
- local modules = import 'modules';
43
+ // Import the library directly from node_modules
44
+ local aws = import '@c6fc/spellcraft-aws-auth/module.libsonnet';
58
45
 
59
46
  {
60
- // The 'awsauth' module provides a native function `getCallerIdentity()`.
61
- // We call it here and direct its output to a file named 'aws-identity.json'.
62
- 'aws-identity.json': modules.awsauth.getCallerIdentity(),
47
+ // Use functions provided by the module
48
+ 'aws-identity.json': aws.getCallerIdentity(),
63
49
 
64
- // We can also create YAML files. SpellCraft has built-in handlers for common types.
65
50
  'config.yaml': {
66
51
  apiVersion: 'v1',
67
52
  kind: 'ConfigMap',
68
- metadata: {
69
- name: 'my-app-config',
70
- },
53
+ metadata: { name: 'my-app-config' },
71
54
  data: {
55
+ // Use built-in native functions
72
56
  region: std.native('envvar')('AWS_REGION') || 'us-east-1',
73
- // The result of the native function is just data. We can reuse it!
74
- callerArn: modules.awsauth.getCallerIdentity().Arn,
57
+ callerArn: aws.getCallerIdentity().Arn,
75
58
  },
76
59
  },
77
60
  }
78
61
  ```
79
62
 
80
- ### 4. Generate the Artifacts
63
+ ### 4. Generate Artifacts
81
64
 
82
- Use the `generate` command to render your `.jsonnet` file into the `render/` directory.
65
+ Run the generator. SpellCraft automatically detects installed plugins in your `package.json`, registers their native functions, and renders your configuration.
83
66
 
84
67
  ```sh
85
68
  npx spellcraft generate manifest.jsonnet
86
69
 
87
70
  # Expected Output:
88
- # [+] Linked @c6fc/spellcraft-aws-auth as awsauth.libsonnet
89
- # ...
90
- # [+] Registered native functions [getCallerIdentity, ...] to modules.awsauth
91
- # [+] Evaluating Jsonnet file manifest.jsonnet
71
+ # [+] Evaluating Jsonnet file: .../manifest.jsonnet
92
72
  # [+] Writing files to: render
93
73
  # -> aws-identity.json
94
74
  # -> config.yaml
95
75
  # [+] Generation complete.
96
76
  ```
97
77
 
98
- Check your `render/` directory. You will find two files created from your single source of truth!
99
-
100
- ```
101
- .
102
- ├── manifest.jsonnet
103
- ├── node_modules/
104
- ├── package.json
105
- └── render/
106
- ├── aws-identity.json
107
- └── config.yaml
108
- ```
78
+ ---
109
79
 
110
- ## The SpellCraft CLI
80
+ ## Rapid Prototyping (Local Modules)
111
81
 
112
- The `spellcraft` CLI is your primary interface for managing modules and generating files.
82
+ Sometimes you need a custom function just for your current project, and you don't want to publish a full NPM package. SpellCraft provides a **Local Magic** folder for this.
113
83
 
114
- ### Core Commands
84
+ 1. Create a folder named `spellcraft_modules` in your project root.
85
+ 2. Create a JavaScript file, e.g., `spellcraft_modules/utils.js`:
115
86
 
116
- - `generate <filename>`: Renders a `.jsonnet` file and writes the output to the `render/` directory.
117
- - `importModule <npmPackage> [name]`: Installs an NPM package and links it as a SpellCraft module. If `[name]` is omitted, it uses the default name defined by the module.
87
+ ```javascript
88
+ // spellcraft_modules/utils.js
89
+ exports.shout = (text) => text.toUpperCase() + "!!!";
90
+ exports.add = (a, b) => a + b;
118
91
 
119
- ### Extensible CLI
92
+ // Use standard functions to access 'this', which is extended by plugins:
93
+ exports.know_thyself = function() {
94
+ this.aws.getCallerIdentity()
95
+ }
96
+ ```
120
97
 
121
- Modules can extend the SpellCraft CLI with their own custom commands. For example, after importing `@c6fc/spellcraft-aws-auth`, you gain new AWS-related commands.
98
+ 3. In your Jsonnet file, import `modules` to access your exported functions:
122
99
 
123
- Run `npx spellcraft --help` to see all available commands, including those added by modules.
100
+ ```jsonnet
101
+ // Import the automatically generated local module aggregator
102
+ local modules = import 'modules';
124
103
 
125
- ```sh
126
- $ npx spellcraft --help
127
-
128
- # ... output showing core commands and module-added commands ...
129
- Commands:
130
- spellcraft generate <filename> Generates files from a configuration
131
- spellcraft importModule <npmPackage> [name] Configures the current project to use a SpellCraft module
132
- spellcraft aws-identity Display the AWS IAM identity of the SpellCraft context # Added by a module
133
- spellcraft aws-exportcredentials Export the current credentials as environment variables # Added by a module
104
+ {
105
+ 'test.json': {
106
+ // Access your local JS functions here
107
+ // Our file was named 'utils.js', so the exported
108
+ // functions are accessed via 'modules.utils'.
109
+ message: modules.utils.shout("hello world"),
110
+ sum: modules.utils.add(10, 5)
111
+ }
112
+ }
134
113
  ```
135
114
 
136
- ## Programmatic Usage (API)
115
+ ---
116
+
117
+ ## The SpellCraft CLI
118
+
119
+ The CLI is automatically extended by installed modules.
137
120
 
138
- For more advanced workflows, such as integration into larger automation scripts, you can use the `SpellFrame` class directly in your Node.js code.
121
+ * `spellcraft generate <filename>`: Renders a Jsonnet file to the `render/` directory.
122
+ * `spellcraft --help`: Lists all available commands, including those added by plugins (e.g., `spellcraft aws-identity`).
139
123
 
140
- The typical flow is:
141
- 1. Instantiate `SpellFrame`.
142
- 2. Run module initializers with `init()`.
143
- 3. Render the Jsonnet file with `render()`.
144
- 4. Write the resulting object to disk with `write()`.
124
+ ---
125
+
126
+ ## Programmatic API
127
+
128
+ You can embed SpellCraft into your own Node.js scripts for advanced automation.
145
129
 
146
130
  ```javascript
147
- // my-automation-script.js
148
131
  const { SpellFrame } = require('@c6fc/spellcraft');
149
132
  const path = require('path');
150
133
 
151
- // 1. Instantiate the SpellFrame
152
- // Options allow you to customize output paths, cleaning behavior, etc.
153
- const frame = new SpellFrame({
154
- renderPath: "dist", // Output to 'dist/' instead of 'render/'
155
- cleanBeforeRender: true,
156
- });
134
+ const frame = new SpellFrame();
157
135
 
158
136
  (async () => {
159
-
160
- // 2. Initialize modules before rendering.
137
+ // 1. Initialize: Scans package.json for plugins and loads them
161
138
  await frame.init();
162
139
 
163
- // 3. Render the master Jsonnet file
164
- const manifest = await frame.render(path.resolve('./manifest.jsonnet'));
165
-
166
- // The result is available in memory
167
- console.log('Rendered Manifest:', JSON.stringify(manifest, null, 2));
140
+ // 2. Render: Evaluates the Jsonnet
141
+ // Note: The result is a pure JS object
142
+ const result = await frame.render(path.resolve('./manifest.jsonnet'));
143
+ console.log(result);
168
144
 
169
- // 4. Write the manifest object to the filesystem. Defaults to the contents of
170
- // the most recent 'render'.
145
+ // 3. Write: Outputs files to disk (applying JSON/YAML transformations)
171
146
  frame.write();
147
+ })();
148
+ ```
172
149
 
173
- console.log('Successfully wrote files to the dist/ directory!');
150
+ ## Creating Modules
174
151
 
175
- })();
152
+ A SpellCraft module is simply an NPM package with specific metadata. You can get a head-start with:
153
+ ```bash
154
+ npm init spellcraft-module @your_org/your_module
176
155
  ```
177
156
 
178
- ## Top-tier spellframe extensions by SpellCraft authors:
157
+ Learn more at [**create-spellcraft-module**](https://www.npmjs.com/package/create-spellcraft-module)
179
158
 
180
- It can be hard to conceptualize what SpellCraft is capable of by just looking at the engine itself. Here are a set of SpellCraft modules that demonstrate the extensibility of the engine:
159
+ ## Community Modules
181
160
 
182
- |Package|Description|
161
+ | Package | Description |
183
162
  |---|---|
184
- |[**@c6fc/spellcraft-aws-auth**](https://www.npmjs.com/package/@c6fc/spellcraft-aws-auth)|Exposes the full power of the AWS SDK for JavaScript to your SpellFrames, including native support for common AWS credential sources and role-chaining.|
185
- |[**@c6fc/spellcraft-terraform**](https://www.npmjs.com/package/@c6fc/spellcraft-terraform)|Brings Terraform into your SpellCraft CLI and SpellFrames, allowing you to directly deploy the results of your Spells.|
186
- |[**@c6fc/spellcraft-packer**](https://www.npmjs.com/package/@c6fc/spellcraft-packer)|Brings Packer into SpellCraft CLI and SpellFrames, allowing you to run builds against the results of your Spells.|
187
- |[**@c6fc/spellcraft-aws-terraform**](https://www.npmjs.com/package/@c6fc/spellcraft-aws-terraform)|Contains shortcuts for common use-cases when using Terraform to deploy configurations to AWS. Including backend bootstrapping, artifact repositories, and remote state access. It also demonstrates how modules can build on the capabilities of other modules.|
188
- |[**@c6fc/spellcraft-aws-s3**](https://www.npmjs.com/package/@c6fc/spellcraft-aws-s3)|A simple but powerful module for creating AWS S3 buckets. It uses secure defaults, exposes common use-cases as optional 'types', and grants extensive control with minimal code.|
163
+ | [**@c6fc/spellcraft-aws-auth**](https://www.npmjs.com/package/@c6fc/spellcraft-aws-auth) | AWS SDK authentication and API calls directly from Jsonnet. |
164
+ | [**@c6fc/spellcraft-terraform**](https://www.npmjs.com/package/@c6fc/spellcraft-terraform) | Terraform integration and state management. |
189
165
 
166
+ ---
190
167
 
191
- ## Creating Your Own Modules
168
+ ## License
192
169
 
193
- When you're ready to start writing your own modules and unleashing the true power of SpellCraft, check out **[create-spellcraft-module](https://www.npmjs.com/package/@c6fc/spellcraft)**
170
+ MIT © [Brad Woodward](https://github.com/c6fc)
package/bin/spellcraft.js CHANGED
@@ -1,46 +1,16 @@
1
1
  #! /usr/bin/env node
2
2
 
3
- /**
4
- * @fileOverview SpellCraft CLI tool.
5
- * This script provides a command-line interface for interacting with the SpellFrame
6
- * rendering engine. It allows users to generate configurations from Jsonnet files
7
- * and manage SpellCraft modules.
8
- * @module spellcraft-cli
9
- */
10
-
11
3
  'use strict';
12
4
 
13
5
  const yargs = require('yargs');
14
6
  const colors = require('@colors/colors');
15
7
  const { hideBin } = require('yargs/helpers');
16
8
  const { SpellFrame } = require('../src/index.js');
9
+ const DocGenerator = require('../src/doc-generator');
17
10
 
18
11
  const spellframe = new SpellFrame();
19
12
 
20
- // --- JSDoc Blocks for CLI Commands ---
21
- // These blocks define the commands for JSDoc.
22
- // They are not directly attached to yargs code but describe the CLI's public interface.
23
-
24
- /**
25
- * Generates files from a Jsonnet configuration.
26
- * This command processes a specified Jsonnet configuration file using the SpellFrame engine,
27
- * renders the output, and writes the resulting files to the configured directory.
28
- *
29
- * **Usage:** `spellcraft generate <filename>`
30
- *
31
- * @function generate
32
- * @name module:spellcraft-cli.generate
33
- * @param {object} argv - The arguments object provided by yargs.
34
- * @param {string} argv.filename The path to the Jsonnet configuration file to consume. (Required)
35
- *
36
- * @example
37
- * spellcraft generate ./myconfig.jsonnet
38
- */
39
-
40
- // --- End of JSDoc Blocks for CLI Commands ---
41
-
42
13
  (async () => {
43
- // No JSDoc for setupCli as it's an internal helper
44
14
  function setupCli(sfInstance) {
45
15
  let cli = yargs(hideBin(process.argv))
46
16
  .usage("Syntax: $0 <command> [options]")
@@ -52,6 +22,12 @@ const spellframe = new SpellFrame();
52
22
  console.log("[~] That's too arcane. (Unrecognized command)");
53
23
  })
54
24
 
25
+ .command("doc", "Generates Markdown documentation for the current module and updates README.md", () => {},
26
+ (argv) => {
27
+ const generator = new DocGenerator(process.cwd());
28
+ generator.generate();
29
+ })
30
+
55
31
  .command("generate <filename>", "Generates files from a configuration", (yargsInstance) => {
56
32
  return yargsInstance.positional('filename', {
57
33
  describe: 'Jsonnet configuration file to consume',
@@ -63,23 +39,17 @@ const spellframe = new SpellFrame();
63
39
  description: 'Leave temporary modules intact after rendering'
64
40
  });
65
41
  },
66
- async (argv) => { // No JSDoc for internal handler
67
- // try {
68
- if (argv['s']) {
69
- sfInstance.cleanModulesAfterRender = false;
70
- }
42
+ async (argv) => {
43
+ if (argv['s']) {
44
+ sfInstance.cleanModulesAfterRender = false;
45
+ }
71
46
 
72
- await sfInstance.init();
73
- await sfInstance.render(argv.filename);
74
- await sfInstance.write();
75
- console.log("[+] Generation complete.");
76
- /*} catch (error) {
77
- console.error(`[!] Error during generation: ${error.message.red}`);
78
- process.exit(1);
79
- }*/
47
+ await sfInstance.init();
48
+ await sfInstance.render(argv.filename);
49
+ await sfInstance.write();
50
+ console.log("[+] Generation complete.");
80
51
  })
81
52
 
82
- // No JSDoc for CLI extensions loop if considered internal detail
83
53
  if (sfInstance.cliExtensions && sfInstance.cliExtensions.length > 0) {
84
54
  sfInstance.cliExtensions.forEach((extensionFn) => {
85
55
  if (typeof extensionFn === 'function') {
package/lib/spellcraft CHANGED
@@ -1,6 +1,4 @@
1
1
  {
2
- local sonnetry = self,
3
-
4
2
  envvar(name):: std.native("envvar")(name),
5
3
  path():: std.native("path")()
6
4
  }
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "name": "@c6fc/spellcraft",
9
9
  "description": "Extensible JSonnet CLI platform",
10
- "version": "0.1.2",
10
+ "version": "0.1.3",
11
11
  "main": "src/index.js",
12
12
  "directories": {
13
13
  "lib": "lib"
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "scripts": {
19
19
  "test": "node src/test.js",
20
- "doc": "jsdoc -c jsdoc.json --verbose"
20
+ "doc": "node src/doc-generator.js"
21
21
  },
22
22
  "repository": {
23
23
  "type": "git",
@@ -33,7 +33,6 @@
33
33
  },
34
34
  "homepage": "https://github.com/c6fc/spellcraft#readme",
35
35
  "devDependencies": {
36
- "clean-jsdoc-theme": "^4.3.0",
37
- "jsdoc": "^4.0.4"
36
+ "clean-jsdoc-theme": "^4.3.0"
38
37
  }
39
38
  }
@@ -0,0 +1,208 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class DocGenerator {
5
+ constructor(baseDir) {
6
+ this.baseDir = baseDir;
7
+ }
8
+
9
+ generate() {
10
+ const readmePath = path.join(this.baseDir, 'README.md');
11
+ if (!fs.existsSync(readmePath)) {
12
+ console.error("[!] No README.md found.");
13
+ return;
14
+ }
15
+
16
+ let readmeContent = fs.readFileSync(readmePath, 'utf-8');
17
+ const apiDocs = this.parseJsonnetDocs();
18
+ const cliDocs = this.parseCliDocs();
19
+
20
+ readmeContent = this.replaceSection(readmeContent, 'API', apiDocs);
21
+ readmeContent = this.replaceSection(readmeContent, 'CLI', cliDocs);
22
+
23
+ fs.writeFileSync(readmePath, readmeContent);
24
+ console.log("[+] README.md updated with generated documentation.");
25
+ }
26
+
27
+ // Helper to replace content between <!-- SPELLCRAFT_DOCS_XYZ_START --> tags
28
+ replaceSection(content, sectionName, newContent) {
29
+ const startTag = `<!-- SPELLCRAFT_DOCS_${sectionName}_START -->`;
30
+ const endTag = `<!-- SPELLCRAFT_DOCS_${sectionName}_END -->`;
31
+ const regex = new RegExp(`${startTag}[\\s\\S]*?${endTag}`, 'g');
32
+
33
+ if (!regex.test(content)) {
34
+ console.log(`[-] No tags exist for ${sectionName}. Skipping.`);
35
+ // Do nothing if the tags don't exist.
36
+ return content;
37
+ }
38
+
39
+ return content.replace(regex, `${startTag}\n${newContent}\n${endTag}`);
40
+ }
41
+
42
+ parseJsonnetDocs() {
43
+ const libPath = path.join(this.baseDir, 'module.libsonnet');
44
+ if (!fs.existsSync(libPath)) return '';
45
+
46
+ const content = fs.readFileSync(libPath, 'utf-8');
47
+ // Regex to find /** comments */ followed by a function definition
48
+ // Captures: 1=Comment content, 2=FunctionName, 3=Args
49
+ const regex = /\/\*\*([\s\S]*?)\*\/\s*\n\s*([\w]+)\(([^)]*)\)/g;
50
+
51
+ let match;
52
+ let markdown = "## API Reference\n\n";
53
+
54
+ while ((match = regex.exec(content)) !== null) {
55
+ const rawCommentLines = match[1].split('\n').map(line =>
56
+ // Remove the " * " from the start of lines
57
+ line.replace(/^\s*\*\s?/, '')
58
+ );
59
+
60
+ const funcName = match[2];
61
+ const args = match[3];
62
+
63
+ let description = [];
64
+ let params = [];
65
+ let examples = []; // Array of arrays (one per example block)
66
+ let currentExampleBlock = null;
67
+ let mode = 'description';
68
+
69
+ rawCommentLines.forEach(line => {
70
+ const trimmed = line.trim();
71
+
72
+ // 1. Detect new @example block
73
+ if (trimmed.startsWith('@example')) {
74
+ mode = 'example';
75
+ currentExampleBlock = []; // Start a new container
76
+ examples.push(currentExampleBlock);
77
+ return; // Skip the tag line itself
78
+ }
79
+
80
+ // 2. Detect Metadata tags (@param, @return)
81
+ // We handle these regardless of mode, assuming they aren't part of the code example
82
+ if (trimmed.startsWith('@param') || trimmed.startsWith('@return')) {
83
+ // Strip the @ and format as a list item
84
+ params.push(`- ${trimmed.substring(1)}`);
85
+ return;
86
+ }
87
+
88
+ // 3. Capture Content
89
+ if (mode === 'example') {
90
+ // Add line to the currently active example block
91
+ if (currentExampleBlock) {
92
+ currentExampleBlock.push(line);
93
+ }
94
+ } else {
95
+ // Add line to general description
96
+ description.push(line);
97
+ }
98
+ });
99
+
100
+ // --- Build Markdown Output ---
101
+
102
+ markdown += `### \`${funcName}(${args})\`\n\n`;
103
+
104
+ // 1. Description
105
+ if (description.length > 0) {
106
+ markdown += description.join('\n').trim() + "\n\n";
107
+ }
108
+
109
+ // 2. Parameters / Returns
110
+ if (params.length > 0) {
111
+ markdown += params.join('\n') + "\n\n";
112
+ }
113
+
114
+ // 3. Examples (Loop through the array)
115
+ if (examples.length > 0) {
116
+ markdown += "**Examples:**\n\n";
117
+ examples.forEach(exBlock => {
118
+ // Polish: Join lines and trim empty leading/trailing newlines
119
+ const code = exBlock.join('\n').trim();
120
+ if (code.length > 0) {
121
+ markdown += "```jsonnet\n";
122
+ markdown += code + "\n";
123
+ markdown += "```\n\n";
124
+ }
125
+ });
126
+ }
127
+
128
+ markdown += "---\n";
129
+ }
130
+ return markdown;
131
+ }
132
+
133
+ parseCliDocs() {
134
+ const jsPath = path.join(this.baseDir, 'module.js');
135
+ if (!fs.existsSync(jsPath)) return '';
136
+
137
+ try {
138
+ // Load the module (Bypass cache to ensure fresh read)
139
+ delete require.cache[require.resolve(jsPath)];
140
+ const moduleExports = require(jsPath);
141
+ const meta = moduleExports._spellcraft_metadata;
142
+
143
+ // Guard clauses
144
+ if (!meta || !meta.cliExtensions || typeof meta.cliExtensions !== 'function') {
145
+ return '';
146
+ }
147
+
148
+ const capturedCommands = [];
149
+
150
+ // Create a Proxy/Mock object to intercept yargs calls
151
+ const mockYargs = {
152
+ command: (command, description, ...args) => {
153
+ // Capture the essential info
154
+ capturedCommands.push({ command, description });
155
+ return mockYargs; // Return self to allow chaining .command().command()
156
+ },
157
+
158
+ // Stub out other common yargs methods so the script doesn't crash
159
+ // if the module uses .usage(), .option(), etc.
160
+ usage: () => mockYargs,
161
+ scriptName: () => mockYargs,
162
+ demandCommand: () => mockYargs,
163
+ recommendCommands: () => mockYargs,
164
+ strict: () => mockYargs,
165
+ showHelpOnFail: () => mockYargs,
166
+ help: () => mockYargs,
167
+ alias: () => mockYargs,
168
+ version: () => mockYargs,
169
+ epilogue: () => mockYargs,
170
+ option: () => mockYargs,
171
+ positional: () => mockYargs,
172
+ group: () => mockYargs,
173
+ };
174
+
175
+ // Mock SpellFrame (The second argument passed to cliExtensions)
176
+ // We mock this just in case the extension tries to read properties from it immediately
177
+ const mockSpellFrame = {
178
+ init: async () => {},
179
+ render: async () => {},
180
+ write: () => {},
181
+ };
182
+
183
+ // Execute the function!
184
+ meta.cliExtensions(mockYargs, mockSpellFrame);
185
+
186
+ // Generate Markdown
187
+ if (capturedCommands.length === 0) return '';
188
+
189
+ let markdown = "## CLI Commands\n\n";
190
+
191
+ capturedCommands.forEach(c => {
192
+ // If description is explicitly false (hidden command), skip it
193
+ if (c.description === false) return;
194
+
195
+ markdown += `- **\`spellcraft ${c.command}\`**\n`;
196
+ markdown += ` ${c.description}\n`;
197
+ });
198
+
199
+ return markdown;
200
+
201
+ } catch (e) {
202
+ console.warn(`[!] Failed to parse CLI docs from module.js: ${e.message}`);
203
+ return '';
204
+ }
205
+ }
206
+ }
207
+
208
+ module.exports = DocGenerator;
package/src/index.js CHANGED
@@ -133,11 +133,6 @@ exports.SpellFrame = class SpellFrame {
133
133
  }
134
134
  }
135
135
 
136
- /**
137
- * REFACTOR: Scans the project's package.json for dependencies.
138
- * If a dependency has a 'spellcraft' key in its package.json,
139
- * load its JS entrypoint and register native functions safely.
140
- */
141
136
  loadPluginsFromDependencies() {
142
137
  const packageJsonPath = path.join(baseDir, 'package.json');
143
138
  if (!fs.existsSync(packageJsonPath)) return;
@@ -175,11 +170,6 @@ exports.SpellFrame = class SpellFrame {
175
170
  });
176
171
  }
177
172
 
178
- /**
179
- * Scans the local 'spellcraft_modules' directory.
180
- * 1. Registers JS exports as native functions (prefixed with 'local_<filename>_').
181
- * 2. Generates a .spellcraft/modules.libsonnet file to allow `import 'modules'`.
182
- */
183
173
  loadLocalMagicModules() {
184
174
  const localModulesDir = path.join(baseDir, 'spellcraft_modules');
185
175
  const generatedDir = path.join(baseDir, '.spellcraft');
@@ -251,11 +241,6 @@ exports.SpellFrame = class SpellFrame {
251
241
  fs.writeFileSync(aggregateFile, finalContent, 'utf-8');
252
242
  }
253
243
 
254
- /**
255
- * REFACTOR: Loads a specific plugin JS file.
256
- * Namespaces native functions using the package name to prevent collisions.
257
- * e.g., @c6fc/spellcraft-aws-auth exports 'aws' -> registered as '@c6fc/spellcraft-aws-auth:aws'
258
- */
259
244
  loadPlugin(packageName, jsMainPath) {
260
245
  if (!jsMainPath || !fs.existsSync(jsMainPath)) return;
261
246
 
@@ -328,12 +313,7 @@ exports.SpellFrame = class SpellFrame {
328
313
 
329
314
  return this.lastRender;
330
315
  }
331
-
332
- // Removed: importSpellCraftModuleFromNpm
333
- // Removed: loadModulesFromModuleDirectory
334
- // Removed: loadModulesFromPackageList
335
- // Removed: loadModuleByName (file copier)
336
-
316
+
337
317
  write(filesToWrite = this.lastRender) {
338
318
  if (!filesToWrite || typeof filesToWrite !== 'object') return this;
339
319
 
package/jsdoc.json DELETED
@@ -1,32 +0,0 @@
1
- {
2
- "tags": {
3
- "allowUnknownTags": true
4
- },
5
- "plugins": ["plugins/markdown"],
6
- "opts": {
7
- "destination": "./docs"
8
- },
9
- "source": {
10
- "include": [
11
- "src/",
12
- "bin/"
13
- ],
14
- "includePattern": ".",
15
- "excludePattern": "\\.bak$"
16
- },
17
- "opts": {
18
- "destination": "./docs/",
19
- "encoding": "utf8",
20
- "private": true,
21
- "recurse": false,
22
- "readme": "README.md",
23
- "template": "node_modules/clean-jsdoc-theme"
24
- },
25
- "markdown": {
26
- "hardwrap": false,
27
- "idInHeadings": true
28
- },
29
- "theme_opts": {
30
- "default_theme": "dark"
31
- }
32
- }