spyne-cli 0.4.1 → 0.6.0

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
@@ -1,3 +1,105 @@
1
- ## spyne-cli
2
- ### Spyne Command Line Tool
3
- Generates spyne objects and saves them to standard spyne application directories.
1
+ # Spyne CLI
2
+
3
+ `spyne-cli` is a command-line utility designed to streamline the process of generating and managing applications built using the [SpyneJS](https://github.com/spynejs/spynejs) framework. It simplifies the creation of `ViewStream`, `Channel`, and `SpyneTrait` classes, making it easier to build scalable and modular single-page applications.
4
+
5
+ ## Features
6
+
7
+ - Create a new SpyneJS application with a single command.
8
+ - Generate `ViewStream`, `Channel`, and `SpyneTrait` classes based on user prompts.
9
+ - Easily extend and customize applications.
10
+ - Built-in support for channel-driven development.
11
+
12
+ ## Installation
13
+
14
+ To install the `spyne-cli`, you need to have Node.js and npm installed on your system.
15
+
16
+ 1. Install the package globally:
17
+
18
+ ```bash
19
+ npm install -g @spynejs/spyne-cli
20
+ ```
21
+
22
+
23
+ ## Usage
24
+
25
+ ### Creating a New SpyneJS Application
26
+
27
+ To create a new SpyneJS application, run:
28
+
29
+ ```bash
30
+ npx spyne-cli create-app <app-name>
31
+ ```
32
+
33
+ This will generate the necessary project files in the specified `<app-name>` directory.
34
+
35
+ ### Generating Components
36
+
37
+ You can use `spyne-cli` to generate new `ViewStream`, `Channel`, and `SpyneTrait` components:
38
+
39
+ - **ViewStream**: To generate a new `ViewStream`, use:
40
+
41
+ ```bash
42
+ npx spyne-cli generate viewstream <view-name>
43
+ ```
44
+
45
+ - **Channel**: To generate a new `Channel`, use:
46
+
47
+ ```bash
48
+ npx spyne-cli generate channel <channel-name>
49
+ ```
50
+
51
+ - **SpyneTrait**: To generate a new `SpyneTrait`, use:
52
+
53
+ ```bash
54
+ npx spyne-cli generate spynetrait <trait-name>
55
+ ```
56
+
57
+ ### Example
58
+
59
+ To create a new application and add a `ViewStream` component:
60
+
61
+ 1. Create a new application:
62
+
63
+ ```bash
64
+ npx spyne-cli create-app my-spyne-app
65
+ ```
66
+
67
+ 2. Navigate to the application directory:
68
+
69
+ ```bash
70
+ cd my-spyne-app
71
+ ```
72
+
73
+ 3. Generate a `ViewStream` component:
74
+
75
+ ```bash
76
+ npx spyne-cli generate viewstream MyView
77
+ ```
78
+
79
+ ## Configuration
80
+
81
+ The `spyne-cli` supports customization via the `spyne.config.js` file located in your project’s root directory. You can modify the config file to control the file structure, template generation, and more.
82
+
83
+ ### Example `spyne.config.js`
84
+
85
+ ```javascript
86
+ module.exports = {
87
+ templates: {
88
+ viewstream: './templates/viewstream.hbs',
89
+ channel: './templates/channel.hbs',
90
+ spynetrait: './templates/spynetrait.hbs',
91
+ },
92
+ outputDir: './src/components',
93
+ };
94
+ ```
95
+
96
+ ## Contributing
97
+
98
+ Contributions are welcome! Please feel free to submit a Pull Request or open an Issue on the [GitHub repository](https://github.com/spynejs/spyne-cli).
99
+
100
+ ## License
101
+
102
+ This project is licensed under the MIT License.
103
+ ```
104
+
105
+ You can now paste this content directly into your `README.md` file. Let me know if you need further assistance!
package/index.js CHANGED
@@ -1,24 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import {SpyneCliUI} from './src/ui.js';
2
+
3
+ import { SpyneCliUI } from './src/ui.js';
3
4
  import clear from 'clear';
4
- import SpyneAppCreator from './src/spyne-app-creator.js';
5
+ import { createNewApp } from './src/spyne-starter-app-create.js';
5
6
  import SpyneFilePrompt from './src/spyne-file-prompt.js';
6
- const args = process.argv;
7
-
8
7
 
9
- let spyneAppCreator = new SpyneAppCreator(args);
10
- const {createAppBool} = spyneAppCreator;
8
+ const command = process.argv[2];
9
+ const appName = process.argv[3];
11
10
 
12
- const startPromptFn = async()=>{
11
+ const startPromptFn = async () => {
13
12
  clear();
14
13
  SpyneCliUI.title();
15
14
  const spyneFilePrompt = new SpyneFilePrompt();
16
15
  await spyneFilePrompt.startPrompt();
17
- }
16
+ };
18
17
 
19
- if (createAppBool){
20
- spyneAppCreator.generateResponse();
18
+ if (command === 'new' && appName) {
19
+ // We can use an immediately-invoked async function
20
+ // OR top-level await if your Node version supports it.
21
+ (async () => {
22
+ try {
23
+ await createNewApp(appName);
24
+ } catch (err) {
25
+ console.error('Failed to create new application:', err.message);
26
+ process.exit(1);
27
+ }
28
+ })();
21
29
  } else {
22
30
  startPromptFn();
23
31
  }
24
-
package/mocha.conf.cjs ADDED
@@ -0,0 +1,16 @@
1
+ // mocha.conf.js
2
+
3
+ module.exports = {
4
+ // Look for test files in the `tests` folder
5
+ spec: 'tests/**/*.test.js',
6
+
7
+ exclude: [
8
+ 'tests/create-spyne-app-test.js'
9
+
10
+ ],
11
+ // You can also specify mocha options here
12
+ extension: ['js'], // The file extensions Mocha should look for
13
+ ui: 'bdd', // BDD-style (describe/it)
14
+ timeout: 5000, // Test timeout in milliseconds
15
+ reporter: 'spec', // The built-in "spec" reporter
16
+ };
package/package.json CHANGED
@@ -4,13 +4,13 @@
4
4
  "spyne-cli": "index.js"
5
5
  },
6
6
  "type": "module",
7
- "version": "0.4.1",
7
+ "version": "0.6.0",
8
8
  "description": "Generates spyne objects and saves them to standard spyne.",
9
9
  "main": "index.js",
10
10
  "scripts": {
11
11
  "debug": "nodemon --no-stdin index.js",
12
12
  "start": "node index.js",
13
- "test": "echo \"Error: no test specified\" && exit 1"
13
+ "test": "mocha --config mocha.conf.cjs"
14
14
  },
15
15
  "repository": {
16
16
  "type": "git",
@@ -23,23 +23,28 @@
23
23
  },
24
24
  "homepage": "https://github.com/spynejs/spyne-cli#readme",
25
25
  "dependencies": {
26
- "ansi-colors": "^4.1.1",
27
- "boxen": "^6.2.1",
28
- "chalk": "^5.0.0",
29
- "change-case": "^4.1.2",
26
+ "ansi-colors": "^4.1.3",
27
+ "boxen": "^8.0.1",
28
+ "chalk": "^5.4.1",
29
+ "change-case": "^5.4.4",
30
30
  "clear": "^0.1.0",
31
- "enquirer": "^2.3.6",
32
- "figlet": "^1.5.2",
31
+ "enquirer": "^2.4.1",
32
+ "figlet": "^1.8.0",
33
+ "fs-extra": "^11.2.0",
33
34
  "json-stringify-safe": "^5.0.1",
34
- "pkg-dir": "^6.0.1",
35
- "pkg-up": "^4.0.0",
36
- "ramda": "^0.28.0",
37
- "read-pkg": "^7.0.0"
35
+ "ora": "^8.1.1",
36
+ "package-up": "^5.0.0",
37
+ "pkg-dir": "^8.0.0",
38
+ "pkg-up": "^5.0.0",
39
+ "ramda": "^0.30.1",
40
+ "read-pkg": "^9.0.1",
41
+ "recast": "^0.23.9",
42
+ "simple-git": "^3.27.0"
38
43
  },
39
44
  "devDependencies": {
40
- "chai": "^4.3.4",
41
- "mocha": "^9.1.3",
42
- "nodemon": "^2.0.15"
45
+ "chai": "^5.1.2",
46
+ "mocha": "^11.0.1",
47
+ "nodemon": "^3.1.9"
43
48
  },
44
49
  "directories": {
45
50
  "lib": "lib"
@@ -0,0 +1,209 @@
1
+ // SpyneAppCreator.js
2
+
3
+ import c from 'ansi-colors';
4
+ import { exec } from 'child_process';
5
+ import fs from 'fs-extra';
6
+ import path from 'path';
7
+ import ora from 'ora';
8
+ import cliCursor from 'cli-cursor';
9
+
10
+ class SpyneAppCreator {
11
+ constructor(args) {
12
+ this.args = args;
13
+ this.createAppBool = this.checkCreateAppCommand();
14
+ }
15
+
16
+ // Method to check if 'create' command is used with 'appFolder' argument
17
+ checkCreateAppCommand() {
18
+ const createIndex = this.args.indexOf('create');
19
+ if (createIndex !== -1 && this.args[createIndex + 1]) {
20
+ this.appFolder = this.args[createIndex + 1];
21
+ this.parseOptions(createIndex + 2); // Start parsing options after 'appFolder'
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+
27
+ // Method to parse additional command-line options
28
+ parseOptions(startIndex) {
29
+ this.options = {
30
+ templateName: 'starter-app', // Default template
31
+ skipInstall: false,
32
+ skipGit: false,
33
+ };
34
+
35
+ for (let i = startIndex; i < this.args.length; i++) {
36
+ const arg = this.args[i];
37
+ if (arg === '--template' && this.args[i + 1]) {
38
+ this.options.templateName = this.args[i + 1];
39
+ i++; // Skip the next argument since it's the value of --template
40
+ } else if (arg === '--no-install') {
41
+ this.options.skipInstall = true;
42
+ } else if (arg === '--skip-git') {
43
+ this.options.skipGit = true;
44
+ }
45
+ }
46
+ }
47
+
48
+ // Getter for createAppBool
49
+ get createAppBool() {
50
+ return this._createAppBool;
51
+ }
52
+
53
+ // Setter for createAppBool
54
+ set createAppBool(value) {
55
+ this._createAppBool = value;
56
+ }
57
+
58
+ // Method to initiate app creation
59
+ async createApp() {
60
+ const appFolder = this.appFolder;
61
+ const { templateName, skipInstall, skipGit } = this.options;
62
+ const repoUrl = this.getTemplateRepoUrl(templateName);
63
+ const appPath = path.resolve(process.cwd(), appFolder);
64
+
65
+ console.log(c.green(`\nCreating a new Spyne app in ${c.cyan(appPath)} using the "${c.cyan(templateName)}" template...\n`));
66
+
67
+ try {
68
+ // Hide the cursor
69
+ cliCursor.hide();
70
+
71
+ // Clone the repository
72
+ await this.cloneRepository(repoUrl, appFolder);
73
+
74
+ // Remove the .git directory
75
+ await fs.remove(path.join(appFolder, '.git'));
76
+
77
+ // Initialize a new Git repository if not skipped
78
+ if (!skipGit) {
79
+ await this.initGitRepo(appFolder);
80
+ }
81
+
82
+ // Install dependencies if not skipped
83
+ if (!skipInstall) {
84
+ await this.installDependencies(appFolder);
85
+ }
86
+
87
+ // Update package.json
88
+ await this.updatePackageJson(appFolder);
89
+
90
+ // Provide post-installation instructions
91
+ this.printSuccessMessage(appFolder, appPath, skipInstall);
92
+ } catch (error) {
93
+ console.error(c.red(`\nError: ${error.message}`));
94
+ } finally {
95
+ // Show the cursor again
96
+ cliCursor.show();
97
+ }
98
+ }
99
+
100
+ // Method to get the repository URL based on the template name
101
+ getTemplateRepoUrl(templateName) {
102
+ const templates = {
103
+ 'starter-app': 'https://github.com/nybatista/starter-app-alpha.git',
104
+ // Add more templates here if needed
105
+ };
106
+ if (!templates[templateName]) {
107
+ throw new Error(`Template "${templateName}" not found.`);
108
+ }
109
+ return templates[templateName];
110
+ }
111
+
112
+ // Method to clone the repository with spinner
113
+ cloneRepository(repoUrl, appFolder) {
114
+ return new Promise((resolve, reject) => {
115
+ const spinner = ora(c.green('Cloning repository...')).start();
116
+
117
+ exec(`git clone --depth=1 ${repoUrl} ${appFolder}`, (error) => {
118
+ if (error) {
119
+ spinner.fail(c.red('Failed to clone repository.'));
120
+ reject(new Error(`Failed to clone repository: ${error.message}`));
121
+ } else {
122
+ spinner.succeed(c.green('Repository cloned successfully.'));
123
+ resolve();
124
+ }
125
+ });
126
+ });
127
+ }
128
+
129
+ // Method to initialize a new Git repository with spinner
130
+ initGitRepo(appFolder) {
131
+ return new Promise((resolve) => {
132
+ const spinner = ora(c.green('Initializing Git repository...')).start();
133
+
134
+ exec('git --version', (gitError) => {
135
+ if (gitError) {
136
+ spinner.warn(c.yellow('Git is not installed. Skipping Git initialization.'));
137
+ resolve();
138
+ } else {
139
+ exec('git init', { cwd: appFolder }, (initError) => {
140
+ if (initError) {
141
+ spinner.fail(c.red('Failed to initialize Git repository.'));
142
+ } else {
143
+ spinner.succeed(c.green('Git repository initialized.'));
144
+ }
145
+ resolve();
146
+ });
147
+ }
148
+ });
149
+ });
150
+ }
151
+
152
+ // Method to install dependencies with spinner
153
+ installDependencies(appFolder) {
154
+ return new Promise((resolve) => {
155
+ const spinner = ora(c.green('Installing dependencies...')).start();
156
+
157
+ exec('npm install', { cwd: appFolder }, (installError) => {
158
+ if (installError) {
159
+ spinner.fail(c.red('Failed to install dependencies.'));
160
+ resolve(); // Proceed even if installation fails
161
+ } else {
162
+ spinner.succeed(c.green('Dependencies installed successfully.'));
163
+ resolve();
164
+ }
165
+ });
166
+ });
167
+ }
168
+
169
+ // Method to update package.json with spinner
170
+ updatePackageJson(appFolder) {
171
+ return new Promise((resolve, reject) => {
172
+ const spinner = ora(c.green('Updating package.json...')).start();
173
+ const pkgPath = path.join(appFolder, 'package.json');
174
+
175
+ fs.readJson(pkgPath)
176
+ .then((pkg) => {
177
+ pkg.name = path.basename(appFolder);
178
+ return fs.writeJson(pkgPath, pkg, { spaces: 2 });
179
+ })
180
+ .then(() => {
181
+ spinner.succeed(c.green('package.json updated.'));
182
+ resolve();
183
+ })
184
+ .catch((err) => {
185
+ spinner.fail(c.red('Failed to update package.json.'));
186
+ reject(new Error(`Failed to update package.json: ${err.message}`));
187
+ });
188
+ });
189
+ }
190
+
191
+ // Method to print success message
192
+ printSuccessMessage(appFolder, appPath, skipInstall) {
193
+ console.log(c.green(`\nSuccess! Created ${c.cyan(appFolder)} at ${c.cyan(appPath)}\n`));
194
+ console.log('Inside that directory, you can run several commands:\n');
195
+ console.log(` ${c.cyan('npm start')}`);
196
+ console.log(' Starts the development server.\n');
197
+ console.log(` ${c.cyan('npm run build')}`);
198
+ console.log(' Bundles the app into static files for production.\n');
199
+ console.log('We suggest that you begin by typing:\n');
200
+ console.log(` ${c.cyan('cd')} ${appFolder}`);
201
+ if (skipInstall) {
202
+ console.log(` ${c.cyan('npm install')}`);
203
+ }
204
+ console.log(` ${c.cyan('npm start')}\n`);
205
+ console.log(c.green('Happy coding!'));
206
+ }
207
+ }
208
+
209
+ export default SpyneAppCreator;
@@ -97,7 +97,7 @@ export default class SpyneAppCreator {
97
97
  static checkArgs(args){
98
98
  const methodStr = args.length>=3 ? args[2] : false;
99
99
  const folderName = args.length>=4 ? args[3] : undefined;
100
- const createAppBool = methodStr === "create-app";
100
+ const createAppBool = ["create-app", "new", "create", "generate"].includes(methodStr);
101
101
 
102
102
  return {folderName, createAppBool};
103
103
 
@@ -116,4 +116,4 @@ export default class SpyneAppCreator {
116
116
  }
117
117
 
118
118
 
119
- }
119
+ }
@@ -7,6 +7,7 @@ import GeneratePromptInputObject from './templates/generate-prompt-input-object.
7
7
  import GenerateFileString from './templates/generate-file-string.js';
8
8
  import {onSaveSpyneFileToDir} from './utils/file-utils.js';
9
9
  import {generatePromptOutput} from './templates/generate-prompt-output.js';
10
+ import {addChannelToIndexJS} from './utils/add-channel-to-index-file.js';
10
11
 
11
12
  export default class SpyneFilePrompt {
12
13
 
@@ -37,10 +38,23 @@ export default class SpyneFilePrompt {
37
38
  }
38
39
 
39
40
  saveFileAndSendOutput(answers) {
40
- const {fileType, fileName, fileDirectory} = answers;
41
+ const {fileType, fileName, fileDirectory, className} = answers;
41
42
  const {fileString} = new GenerateFileString(fileType, answers);
42
43
  const savedProps = onSaveSpyneFileToDir(fileString, fileName, fileDirectory);
43
- const msgOutput = generatePromptOutput(answers, savedProps, fileString);
44
+
45
+ // default is no channel to be registered, yet
46
+ let channelHasRegistered = false;
47
+
48
+ if (fileType === 'Channel'){
49
+ const savedChannelToIndexProps = addChannelToIndexJS(className, fileName);
50
+
51
+ // check if channel has been registered
52
+ channelHasRegistered = savedChannelToIndexProps.fileHasSaved;
53
+ }
54
+
55
+ const msgOutput = generatePromptOutput(answers, savedProps, fileString, channelHasRegistered);
56
+
57
+
44
58
  console.log(msgOutput);
45
59
  }
46
60
 
@@ -0,0 +1,76 @@
1
+ import c from 'ansi-colors';
2
+ import simpleGit from 'simple-git';
3
+ import { spawn } from 'child_process';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import ora from 'ora';
7
+
8
+ async function installDependencies(appName) {
9
+ const spinner = ora({
10
+ text: c.cyan('Installing dependencies...'),
11
+ spinner: 'dots'
12
+ }).start();
13
+
14
+ return new Promise((resolve, reject) => {
15
+ // Use --silent to suppress npm logs
16
+ const child = spawn('npm', ['install', '--silent'], {
17
+ cwd: appName,
18
+ // stdio: 'ignore' → all output is ignored, the spinner continues unblocked
19
+ stdio: 'ignore',
20
+ });
21
+
22
+ child.on('close', (code) => {
23
+ if (code === 0) {
24
+ spinner.succeed(c.greenBright('Dependencies installed successfully!'));
25
+ resolve();
26
+ } else {
27
+ spinner.fail(c.red(`Failed to install dependencies (exit code: ${code}).`));
28
+ reject(new Error('npm install failed'));
29
+ }
30
+ });
31
+ });
32
+ }
33
+
34
+ export async function createNewApp(appName) {
35
+ const targetDir = path.resolve(process.cwd(), appName);
36
+ const repoUrl = 'https://github.com/nybatista/starter-app-alpha.git';
37
+ const git = simpleGit();
38
+
39
+ console.log(c.cyan(`\nCreating a new Spyne app in ${c.bold(appName)}...`));
40
+
41
+ // 1) Clone repository with Ora spinner
42
+ let spinner = ora({
43
+ text: c.cyan('Cloning starter-app repository...'),
44
+ spinner: 'dots',
45
+ }).start();
46
+
47
+ try {
48
+ await git.clone(repoUrl, targetDir, ['--depth=1']);
49
+ spinner.succeed(c.greenBright('Starter App cloned successfully!'));
50
+ } catch (err) {
51
+ spinner.fail(c.red(`Failed to clone the repository: ${err.message}`));
52
+ process.exit(1);
53
+ }
54
+
55
+ // 2) Remove .git (silent - no user message)
56
+ try {
57
+ fs.rmSync(path.join(targetDir, '.git'), { recursive: true, force: true });
58
+ } catch (err) {
59
+ console.error(c.red(`Failed to remove .git folder: ${err.message}`));
60
+ process.exit(1);
61
+ }
62
+
63
+ // 3) Asynchronous install (silent)
64
+ try {
65
+ await installDependencies(appName);
66
+ } catch (err) {
67
+ console.error(c.red(err.message));
68
+ process.exit(1);
69
+ }
70
+
71
+ // 4) Final success message
72
+ console.log(`\n${c.greenBright('Success!')}`);
73
+ console.log(c.cyan(`Created ${c.bold(appName)} at ${targetDir}\n`));
74
+ console.log(c.greenBright('Next steps:'));
75
+ console.log(c.bgCyan(c.black(` cd ${appName} && npm start`)));
76
+ }
@@ -9,12 +9,12 @@ export class ${props.className} extends ViewStream {
9
9
  }
10
10
 
11
11
  addActionListeners() {
12
- // return nexted array(s)
12
+ // return nested array(s)
13
13
  return [];
14
14
  }
15
15
 
16
16
  broadcastEvents() {
17
- // return nexted array(s)
17
+ // return nested array(s)
18
18
  return [];
19
19
  }
20
20
 
@@ -15,7 +15,8 @@ const {prompt, Select} = enquirer;
15
15
  import c from 'ansi-colors';
16
16
  import * as R from 'ramda';
17
17
  import {getLocalFileDirectory, validateFileDirectory} from '../utils/file-utils.js';
18
- import changeCase from 'change-case';
18
+ import * as changeCase from "change-case";
19
+
19
20
 
20
21
  const {
21
22
  camelCase,
@@ -38,7 +38,7 @@ export default class GeneratePromptInputObject{
38
38
  }
39
39
 
40
40
  static getPromptObjSettings(){
41
- return R.compose(R.head, R.filter(R.propEq('inputType', _inputType)))(promptTypes);
41
+ return R.compose(R.head, R.filter(R.propEq(_inputType, 'inputType')))(promptTypes);
42
42
  }
43
43
 
44
44
  }
@@ -9,9 +9,11 @@ colors.alias('files', colors.green);
9
9
  let _answers;
10
10
  let _savedProps;
11
11
  let _fileStr;
12
-
12
+ let _channelHasRegistered = false;
13
13
 
14
14
  const createChannelRegisterSnippet = () => {
15
+
16
+
15
17
  const {fileName, className, fileDirectory, channelName} = _answers;
16
18
 
17
19
  const filePath = fileDirectory+fileName;
@@ -71,17 +73,25 @@ const outputMessage = ()=>{
71
73
 
72
74
  let messageOutput = '';
73
75
 
74
- const defaultStr = colors.bgBlue(colors.black("File saved successfully."));
76
+ const defaultStr = colors.bgYellow(colors.black(`Successfully saved the ${fileType} file.`));
77
+
78
+ const checkForChannelRegisteredMsg = ()=>{
79
+ if (_channelHasRegistered === true){
80
+ return colors.bgYellow(colors.black(`Channel registered and file saved successfully.`));
81
+ }
82
+ return `${defaultStr}\n${createChannelRegisterSnippet()}`;
83
+ }
84
+
75
85
  const messageHash = {
76
86
 
77
- "ViewStream" : defaultStr,
78
- "DomElement" : defaultStr,
79
- "SpyneTrait" : defaultStr,
80
- "Channel" : `${defaultStr}\n${createChannelRegisterSnippet()}`
87
+ "ViewStream" : ()=>defaultStr,
88
+ "DomElement" : ()=>defaultStr,
89
+ "SpyneTrait" : ()=>defaultStr,
90
+ "Channel" : checkForChannelRegisteredMsg
81
91
  }
82
92
 
83
93
  if (fileHasSaved){
84
- messageOutput = messageHash[fileType];
94
+ messageOutput = messageHash[fileType]();
85
95
  } else {
86
96
 
87
97
  messageOutput = outputFailedMessage();
@@ -94,11 +104,12 @@ const outputMessage = ()=>{
94
104
 
95
105
 
96
106
 
97
- const generatePromptOutput = (answers={}, savedProps={}, fileStr='')=>{
107
+ const generatePromptOutput = (answers={}, savedProps={}, fileStr='', channelHasRegistered=false)=>{
98
108
 
99
109
  _answers = answers;
100
110
  _savedProps = savedProps;
101
111
  _fileStr = fileStr;
112
+ _channelHasRegistered = channelHasRegistered;
102
113
 
103
114
 
104
115
  return outputMessage();
package/src/ui.js CHANGED
@@ -5,7 +5,7 @@ export class SpyneCliUI {
5
5
 
6
6
  constructor() {}
7
7
  static title(){
8
- const figletTxt = figlet.textSync('spyne-cli', {
8
+ const figletTxt = figlet.textSync('spyne-cli 6.0', {
9
9
  horizontalLayout: 'universal smushing'
10
10
  })
11
11
  const chalkOutput = chalk.blue(figletTxt);
@@ -13,4 +13,4 @@ export class SpyneCliUI {
13
13
 
14
14
  }
15
15
 
16
- }
16
+ }
@@ -0,0 +1,116 @@
1
+ // add-channel.js
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import recast from 'recast';
6
+
7
+ const { builders: b } = recast.types;
8
+
9
+ // Export the function so it can be imported elsewhere
10
+ export const addChannelToIndexJS = (channelClassName, channelFileName, indexJSPath="./src/index.js") => {
11
+ try {
12
+ // Read the index.js file content
13
+ const code = fs.readFileSync(indexJSPath, 'utf8');
14
+
15
+ // Parse the code into an AST
16
+ const ast = recast.parse(code);
17
+
18
+ // 1. Add the import statement
19
+ const importDeclaration = b.importDeclaration(
20
+ [b.importSpecifier(b.identifier(channelClassName))],
21
+ b.literal(`channels/${channelFileName}`)
22
+ );
23
+
24
+ // Insert the import declaration after the last import statement
25
+ const body = ast.program.body;
26
+ let lastImportIndex = -1;
27
+ body.forEach((node, index) => {
28
+ if (node.type === 'ImportDeclaration') {
29
+ lastImportIndex = index;
30
+ }
31
+ });
32
+ if (lastImportIndex >= 0) {
33
+ body.splice(lastImportIndex + 1, 0, importDeclaration);
34
+ } else {
35
+ // No import declarations found, add at the top
36
+ body.unshift(importDeclaration);
37
+ }
38
+
39
+ // 2. Add the SpyneApp.registerChannel() call
40
+ const registerCall = b.expressionStatement(
41
+ b.callExpression(
42
+ b.memberExpression(b.identifier('SpyneApp'), b.identifier('registerChannel')),
43
+ [b.newExpression(b.identifier(channelClassName), [])]
44
+ )
45
+ );
46
+
47
+ // Find the correct place to insert the register call
48
+ // After the last existing SpyneApp.registerChannel() call
49
+ let lastRegisterIndex = -1;
50
+ body.forEach((node, index) => {
51
+ if (
52
+ node.type === 'ExpressionStatement' &&
53
+ node.expression.type === 'CallExpression' &&
54
+ node.expression.callee.type === 'MemberExpression' &&
55
+ node.expression.callee.object.name === 'SpyneApp' &&
56
+ node.expression.callee.property.name === 'registerChannel'
57
+ ) {
58
+ lastRegisterIndex = index;
59
+ }
60
+ });
61
+
62
+ if (lastRegisterIndex >= 0) {
63
+ body.splice(lastRegisterIndex + 1, 0, registerCall);
64
+ } else {
65
+ // If no existing register calls, insert after SpyneApp.init(config);
66
+ let initIndex = -1;
67
+ body.forEach((node, index) => {
68
+ if (
69
+ node.type === 'ExpressionStatement' &&
70
+ node.expression.type === 'CallExpression' &&
71
+ node.expression.callee.type === 'MemberExpression' &&
72
+ node.expression.callee.object.name === 'SpyneApp' &&
73
+ node.expression.callee.property.name === 'init'
74
+ ) {
75
+ initIndex = index;
76
+ }
77
+ });
78
+ if (initIndex >= 0) {
79
+ body.splice(initIndex + 1, 0, registerCall);
80
+ } else {
81
+ // Else, add at the end
82
+ body.push(registerCall);
83
+ }
84
+ }
85
+
86
+ // Generate the modified code
87
+ const output = recast.print(ast).code;
88
+
89
+ // Write back to index.js
90
+ fs.writeFileSync(indexJSPath, output, 'utf8');
91
+
92
+ // Return success result
93
+ return {
94
+ fileHasSaved: true,
95
+ errorType: null
96
+ };
97
+ } catch (error) {
98
+ // Determine error type
99
+ let errorType = 'UnknownError';
100
+ if (error.code === 'ENOENT') {
101
+ errorType = 'FileNotFound';
102
+ } else if (error.name === 'SyntaxError') {
103
+ errorType = 'SyntaxError';
104
+ } else if (error.name === 'TypeError') {
105
+ errorType = 'TypeError';
106
+ } else if (error.name === 'PermissionError') {
107
+ errorType = 'PermissionError';
108
+ }
109
+
110
+ // Fail silently and return error information
111
+ return {
112
+ fileHasSaved: false,
113
+ errorType
114
+ };
115
+ }
116
+ };
@@ -65,6 +65,7 @@ const getLocalFileDirectory = (_fileType)=>{
65
65
  ErrorLogger.log(e, 'getFileDirectory')
66
66
  }
67
67
 
68
+
68
69
  return dirPath;
69
70
 
70
71
  }
@@ -1,5 +1,4 @@
1
- import chai from 'chai';
2
- const {expect} = chai;
1
+ import {expect} from 'chai';
3
2
  import path from 'path';
4
3
 
5
4
  import SpyneAppCreator from '../src/spyne-app-creator.js';
@@ -61,4 +60,4 @@ describe('should test app creation methods', () => {
61
60
 
62
61
 
63
62
 
64
- });
63
+ });
@@ -1,7 +1,5 @@
1
1
  import SpyneFilePrompt from '../src/spyne-file-prompt.js';
2
- import chai from 'chai';
3
- const {expect, assert} = chai;
4
-
2
+ import {expect, assert} from 'chai';
5
3
 
6
4
 
7
5
  describe('should test spyne file prompt', () => {
@@ -1,6 +1,4 @@
1
- import chai from 'chai';
2
- const {expect, assert} = chai;
3
- import GenerateFileString from '../src/templates/generate-file-string.js';
1
+ import {expect, assert} from 'chai';import GenerateFileString from '../src/templates/generate-file-string.js';
4
2
 
5
3
  describe('should test file string generator', () => {
6
4
 
@@ -1,7 +1,5 @@
1
- import chai from 'chai';
2
- const {expect, assert} = chai;
3
- import PromptInputField from '../src/templates/generate-prompt-input-fields.js';
4
- import changeCase from 'change-case';
1
+ import {expect, assert} from 'chai';import PromptInputField from '../src/templates/generate-prompt-input-fields.js';
2
+ import * as changeCase from "change-case";
5
3
 
6
4
  const {
7
5
  camelCase,
@@ -95,7 +93,7 @@ describe('should create all necessary prompt input initial, hint and validate fi
95
93
 
96
94
  it('it should generate initial value for directory based on app path', ()=>{
97
95
  let inputType = 'fileDirectory';
98
- const initialViewStreamFileDir = new PromptInputField(inputType, fileType, 'initial').field;
96
+ const initialViewStreamFileDir = new PromptInputField(inputType, fileType, 'initial').field;
99
97
  const initialDomElementFileDir = new PromptInputField(inputType, 'DomElement', 'initial').field;
100
98
  const initialChannelFileDir = new PromptInputField(inputType, 'Channel', 'initial').field;
101
99
  const initialSpyneTraitFileDir = new PromptInputField(inputType, 'SpyneTrait', 'initial').field;
@@ -107,6 +105,7 @@ describe('should create all necessary prompt input initial, hint and validate fi
107
105
 
108
106
 
109
107
  //console.log('process ',{initialFileDir})
108
+
110
109
  expect(initialViewStreamFileDir).to.equal('./src/app/components/')
111
110
  expect(initialDomElementFileDir).to.equal('./src/app/components/')
112
111
  expect(initialChannelFileDir).to.equal('./src/app/channels/')
@@ -1,6 +1,4 @@
1
- import chai from 'chai';
2
- const {expect, assert} = chai;
3
- import PromptInputField from '../src/templates/generate-prompt-input-fields.js';
1
+ import {expect, assert} from 'chai';import PromptInputField from '../src/templates/generate-prompt-input-fields.js';
4
2
 
5
3
  import {MockData} from './mocks/enquirer-data.js';
6
4
 
@@ -1,6 +1,4 @@
1
- import chai from 'chai';
2
- const {expect, assert} = chai;
3
- import GeneratePromptInputObject from '../src/templates/generate-prompt-input-object.js';
1
+ import {expect, assert} from 'chai';import GeneratePromptInputObject from '../src/templates/generate-prompt-input-object.js';
4
2
  import {Data} from '../src/spyne-template-prompts.js';
5
3
  import {MockData} from './mocks/enquirer-data.js';
6
4
  const {promptTypes} = Data;
@@ -1,12 +1,10 @@
1
- import chai from 'chai';
2
- const {expect, assert} = chai;
3
- import {MockData} from './mocks/enquirer-data.js';
1
+ import {expect, assert} from 'chai';import {MockData} from './mocks/enquirer-data.js';
4
2
  import {AnswersData} from './mocks/answers.js';
5
3
  const {answersArr} = AnswersData;
6
4
  import {generatePromptOutput} from '../src/templates/generate-prompt-output.js';
7
5
  import * as R from 'ramda';
8
6
 
9
- const getAnswersByFileType = (fileType) => R.compose(R.head, R.filter(R.propEq('fileType', fileType)))(answersArr)
7
+ const getAnswersByFileType = (fileType) => R.compose(R.head, R.filter(R.propEq(fileType, 'fileType')))(answersArr)
10
8
 
11
9
  const defaultStr = `
12
10
  import {Subject} from 'rxjs';
@@ -1,6 +1,4 @@
1
- import chai from 'chai';
2
- const {expect, assert} = chai;
3
-
1
+ import {expect, assert} from 'chai';
4
2
  describe('should test cli methods', () => {
5
3
 
6
4
  it('should run cli tests', () => {
@@ -1,6 +1,4 @@
1
- import chai from 'chai';
2
- const {expect, assert} = chai;
3
- import {MockData} from '../mocks/enquirer-data.js';
1
+ import {expect, assert} from 'chai';import {MockData} from '../mocks/enquirer-data.js';
4
2
  import {AnswersData} from '../mocks/answers.js';
5
3
  const {answersArr} = AnswersData;
6
4
  import * as R from 'ramda';
@@ -12,7 +10,7 @@ import path from 'path';
12
10
  import {onSaveSpyneFileToDir, copyDirSync, removeDir} from '../../src/utils/file-utils.js';
13
11
 
14
12
 
15
- const getAnswersByFileType = (fileType) => R.compose(R.head, R.filter(R.propEq('fileType', fileType)))(answersArr)
13
+ const getAnswersByFileType = (fileType) => R.compose(R.head, R.filter(R.propEq(fileType, 'fileType')))(answersArr)
16
14
 
17
15
  const defaultStr = `
18
16
  import {Subject} from 'rxjs';
@@ -1,25 +0,0 @@
1
- import {ViewStream} from 'spyne';
2
-
3
- export class MyUiElView extends ViewStream {
4
-
5
- constructor(props={}) {
6
-
7
- super(props);
8
- }
9
-
10
- addActionListeners() {
11
- // return nexted array(s)
12
- return [];
13
- }
14
-
15
- broadcastEvents() {
16
- // return nexted array(s)
17
- return [];
18
- }
19
-
20
- onRendered() {
21
-
22
- }
23
-
24
- }
25
-
@@ -1,25 +0,0 @@
1
- import {ViewStream} from 'spyne';
2
-
3
- export class Sdafsdfaa extends ViewStream {
4
-
5
- constructor(props={}) {
6
-
7
- super(props);
8
- }
9
-
10
- addActionListeners() {
11
- // return nexted array(s)
12
- return [];
13
- }
14
-
15
- broadcastEvents() {
16
- // return nexted array(s)
17
- return [];
18
- }
19
-
20
- onRendered() {
21
-
22
- }
23
-
24
- }
25
-
@@ -1,15 +0,0 @@
1
- ## Spynejs Hello World Application
2
- barebones app preconfigured for building, testing and adding content using the spyne-cli tool
3
-
4
- App includes
5
- * AppView
6
- * Application Folder Structure
7
- * Build, production setup using webpack and sass
8
- * Spyne Console plugin
9
- * Unit testing
10
-
11
- ```
12
- npm run init or npm install
13
- ```
14
-
15
- ### Build better web experiences, faster