spyne-cli 0.4.1 → 0.5.5
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 +105 -3
- package/index.js +8 -2
- package/package.json +16 -13
- package/src/spyne-app-create.js +190 -0
- package/src/spyne-app-creator.js +2 -2
- package/src/spyne-file-prompt.js +16 -2
- package/src/templates/generate-file-string.js +2 -2
- package/src/templates/generate-prompt-input-fields.js +2 -1
- package/src/templates/generate-prompt-input-object.js +1 -1
- package/src/templates/generate-prompt-output.js +19 -8
- package/src/ui.js +2 -2
- package/src/utils/add-channel-to-index-file.js +116 -0
- package/src/utils/file-utils.js +1 -0
- package/tests/generate-prompt-input-fields-methods.test.js +3 -2
- package/tests/generate-prompt-output.test.js +1 -1
- package/src/app/channels/channel-custom.js +0 -1
- package/src/app/components/my-ui-el-view.js +0 -25
- package/src/app/components/sdafsdfaa.js +0 -25
- package/src/h-world-new/README.md +0 -15
package/README.md
CHANGED
|
@@ -1,3 +1,105 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
import {SpyneCliUI} from './src/ui.js';
|
|
3
3
|
import clear from 'clear';
|
|
4
4
|
import SpyneAppCreator from './src/spyne-app-creator.js';
|
|
5
|
+
import SpyneAppCreate from './src/spyne-app-create.js';
|
|
5
6
|
import SpyneFilePrompt from './src/spyne-file-prompt.js';
|
|
6
7
|
const args = process.argv;
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
let spyneAppCreator = new SpyneAppCreator(args);
|
|
10
|
+
//let spyneAppCreator = new SpyneAppCreator(args);
|
|
11
|
+
//const {createAppBool} = spyneAppCreator;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
let spyneAppCreator = new SpyneAppCreate(process.argv.slice(2));
|
|
10
15
|
const {createAppBool} = spyneAppCreator;
|
|
11
16
|
|
|
12
17
|
const startPromptFn = async()=>{
|
|
@@ -17,7 +22,8 @@ const startPromptFn = async()=>{
|
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
if (createAppBool){
|
|
20
|
-
spyneAppCreator.generateResponse();
|
|
25
|
+
//spyneAppCreator.generateResponse();
|
|
26
|
+
spyneAppCreator.createApp();
|
|
21
27
|
} else {
|
|
22
28
|
startPromptFn();
|
|
23
29
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"spyne-cli": "index.js"
|
|
5
5
|
},
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.5.5",
|
|
8
8
|
"description": "Generates spyne objects and saves them to standard spyne.",
|
|
9
9
|
"main": "index.js",
|
|
10
10
|
"scripts": {
|
|
@@ -23,23 +23,26 @@
|
|
|
23
23
|
},
|
|
24
24
|
"homepage": "https://github.com/spynejs/spyne-cli#readme",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"ansi-colors": "^4.1.
|
|
27
|
-
"boxen": "^
|
|
28
|
-
"chalk": "^5.
|
|
29
|
-
"change-case": "^4.
|
|
26
|
+
"ansi-colors": "^4.1.3",
|
|
27
|
+
"boxen": "^8.0.1",
|
|
28
|
+
"chalk": "^5.3.0",
|
|
29
|
+
"change-case": "^5.4.4",
|
|
30
30
|
"clear": "^0.1.0",
|
|
31
|
-
"enquirer": "^2.
|
|
32
|
-
"figlet": "^1.
|
|
31
|
+
"enquirer": "^2.4.1",
|
|
32
|
+
"figlet": "^1.7.0",
|
|
33
|
+
"fs-extra": "^11.2.0",
|
|
33
34
|
"json-stringify-safe": "^5.0.1",
|
|
34
|
-
"
|
|
35
|
-
"pkg-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
35
|
+
"package-up": "^5.0.0",
|
|
36
|
+
"pkg-dir": "^8.0.0",
|
|
37
|
+
"pkg-up": "^5.0.0",
|
|
38
|
+
"ramda": "^0.30.1",
|
|
39
|
+
"read-pkg": "^9.0.1",
|
|
40
|
+
"recast": "^0.23.9"
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
40
43
|
"chai": "^4.3.4",
|
|
41
|
-
"mocha": "^
|
|
42
|
-
"nodemon": "^
|
|
44
|
+
"mocha": "^10.7.3",
|
|
45
|
+
"nodemon": "^3.1.7"
|
|
43
46
|
},
|
|
44
47
|
"directories": {
|
|
45
48
|
"lib": "lib"
|
|
@@ -0,0 +1,190 @@
|
|
|
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
|
+
|
|
8
|
+
class SpyneAppCreator {
|
|
9
|
+
constructor(args) {
|
|
10
|
+
this.args = args;
|
|
11
|
+
this.createAppBool = this.checkCreateAppCommand();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Method to check if 'create' command is used with 'appFolder' argument
|
|
15
|
+
checkCreateAppCommand() {
|
|
16
|
+
const createIndex = this.args.indexOf('create');
|
|
17
|
+
if (createIndex !== -1 && this.args[createIndex + 1]) {
|
|
18
|
+
this.appFolder = this.args[createIndex + 1];
|
|
19
|
+
this.parseOptions(createIndex + 2); // Start parsing options after 'appFolder'
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Method to parse additional command-line options
|
|
26
|
+
parseOptions(startIndex) {
|
|
27
|
+
this.options = {
|
|
28
|
+
templateName: 'starter-app', // Default template
|
|
29
|
+
skipInstall: false,
|
|
30
|
+
skipGit: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
for (let i = startIndex; i < this.args.length; i++) {
|
|
34
|
+
const arg = this.args[i];
|
|
35
|
+
if (arg === '--template' && this.args[i + 1]) {
|
|
36
|
+
this.options.templateName = this.args[i + 1];
|
|
37
|
+
i++; // Skip the next argument since it's the value of --template
|
|
38
|
+
} else if (arg === '--no-install') {
|
|
39
|
+
this.options.skipInstall = true;
|
|
40
|
+
} else if (arg === '--skip-git') {
|
|
41
|
+
this.options.skipGit = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Getter for createAppBool
|
|
47
|
+
get createAppBool() {
|
|
48
|
+
return this._createAppBool;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Setter for createAppBool
|
|
52
|
+
set createAppBool(value) {
|
|
53
|
+
this._createAppBool = value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Method to initiate app creation
|
|
57
|
+
async createApp() {
|
|
58
|
+
const appFolder = this.appFolder;
|
|
59
|
+
const { templateName, skipInstall, skipGit } = this.options;
|
|
60
|
+
const repoUrl = this.getTemplateRepoUrl(templateName);
|
|
61
|
+
const appPath = path.resolve(process.cwd(), appFolder);
|
|
62
|
+
|
|
63
|
+
console.log(c.green(`\nCreating a new Spyne app in ${c.cyan(appPath)} using the "${c.cyan(templateName)}" template...\n`));
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
// Clone the repository
|
|
67
|
+
await this.cloneRepository(repoUrl, appFolder);
|
|
68
|
+
|
|
69
|
+
// Remove the .git directory
|
|
70
|
+
await fs.remove(path.join(appFolder, '.git'));
|
|
71
|
+
|
|
72
|
+
// Initialize a new Git repository if not skipped
|
|
73
|
+
if (!skipGit) {
|
|
74
|
+
await this.initGitRepo(appFolder);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Install dependencies if not skipped
|
|
78
|
+
if (!skipInstall) {
|
|
79
|
+
await this.installDependencies(appFolder);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Update package.json
|
|
83
|
+
await this.updatePackageJson(appFolder);
|
|
84
|
+
|
|
85
|
+
// Provide post-installation instructions
|
|
86
|
+
this.printSuccessMessage(appFolder, appPath, skipInstall);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(c.red(`\nError: ${error.message}`));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Method to get the repository URL based on the template name
|
|
93
|
+
getTemplateRepoUrl(templateName) {
|
|
94
|
+
const templates = {
|
|
95
|
+
'starter-app': 'https://github.com/nybatista/starter-app-alpha.git',
|
|
96
|
+
// Add more templates here if needed
|
|
97
|
+
};
|
|
98
|
+
if (!templates[templateName]) {
|
|
99
|
+
throw new Error(`Template "${templateName}" not found.`);
|
|
100
|
+
}
|
|
101
|
+
return templates[templateName];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Method to clone the repository
|
|
105
|
+
cloneRepository(repoUrl, appFolder) {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
exec(`git clone --depth=1 ${repoUrl} ${appFolder}`, (error) => {
|
|
108
|
+
if (error) {
|
|
109
|
+
reject(new Error(`Failed to clone repository: ${error.message}`));
|
|
110
|
+
} else {
|
|
111
|
+
console.log(c.green('✔ Repository cloned successfully.'));
|
|
112
|
+
resolve();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Method to initialize a new Git repository
|
|
119
|
+
initGitRepo(appFolder) {
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
exec('git --version', (gitError) => {
|
|
122
|
+
if (gitError) {
|
|
123
|
+
console.warn(c.yellow('⚠ Git is not installed. Skipping Git initialization.'));
|
|
124
|
+
resolve();
|
|
125
|
+
} else {
|
|
126
|
+
exec('git init', { cwd: appFolder }, (initError) => {
|
|
127
|
+
if (initError) {
|
|
128
|
+
console.warn(c.yellow('⚠ Failed to initialize Git repository.'));
|
|
129
|
+
} else {
|
|
130
|
+
console.log(c.green('✔ Git repository initialized.'));
|
|
131
|
+
}
|
|
132
|
+
resolve();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Method to install dependencies
|
|
140
|
+
installDependencies(appFolder) {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
console.log(c.green('\nInstalling dependencies...\n'));
|
|
143
|
+
exec('npm install', { cwd: appFolder }, (installError) => {
|
|
144
|
+
if (installError) {
|
|
145
|
+
console.warn(c.yellow('⚠ Failed to install dependencies.'));
|
|
146
|
+
resolve(); // Proceed even if installation fails
|
|
147
|
+
} else {
|
|
148
|
+
console.log(c.green('✔ Dependencies installed successfully.'));
|
|
149
|
+
resolve();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Method to update package.json
|
|
156
|
+
updatePackageJson(appFolder) {
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
const pkgPath = path.join(appFolder, 'package.json');
|
|
159
|
+
fs.readJson(pkgPath)
|
|
160
|
+
.then((pkg) => {
|
|
161
|
+
pkg.name = path.basename(appFolder);
|
|
162
|
+
return fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
163
|
+
})
|
|
164
|
+
.then(() => {
|
|
165
|
+
console.log(c.green('✔ package.json updated.'));
|
|
166
|
+
resolve();
|
|
167
|
+
})
|
|
168
|
+
.catch((err) => reject(new Error(`Failed to update package.json: ${err.message}`)));
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Method to print success message
|
|
173
|
+
printSuccessMessage(appFolder, appPath, skipInstall) {
|
|
174
|
+
console.log(c.green(`\nSuccess! Created ${c.cyan(appFolder)} at ${c.cyan(appPath)}\n`));
|
|
175
|
+
console.log('Inside that directory, you can run several commands:\n');
|
|
176
|
+
console.log(` ${c.cyan('npm start')}`);
|
|
177
|
+
console.log(' Starts the development server.\n');
|
|
178
|
+
console.log(` ${c.cyan('npm run build')}`);
|
|
179
|
+
console.log(' Bundles the app into static files for production.\n');
|
|
180
|
+
console.log('We suggest that you begin by typing:\n');
|
|
181
|
+
console.log(` ${c.cyan('cd')} ${appFolder}`);
|
|
182
|
+
if (skipInstall) {
|
|
183
|
+
console.log(` ${c.cyan('npm install')}`);
|
|
184
|
+
}
|
|
185
|
+
console.log(` ${c.cyan('npm start')}\n`);
|
|
186
|
+
console.log(c.green('Happy coding!'));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default SpyneAppCreator;
|
package/src/spyne-app-creator.js
CHANGED
|
@@ -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 =
|
|
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
|
+
}
|
package/src/spyne-file-prompt.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
|
@@ -9,12 +9,12 @@ export class ${props.className} extends ViewStream {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
addActionListeners() {
|
|
12
|
-
// return
|
|
12
|
+
// return nested array(s)
|
|
13
13
|
return [];
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
broadcastEvents() {
|
|
17
|
-
// return
|
|
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
|
|
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'
|
|
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(
|
|
76
|
+
const defaultStr = colors.bgBlue(colors.black(`Successfully saved the ${fileType} file.`));
|
|
77
|
+
|
|
78
|
+
const checkForChannelRegisteredMsg = ()=>{
|
|
79
|
+
if (_channelHasRegistered === true){
|
|
80
|
+
return colors.bgBlue(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" :
|
|
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 5.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
|
+
};
|
package/src/utils/file-utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chai from 'chai';
|
|
2
2
|
const {expect, assert} = chai;
|
|
3
3
|
import PromptInputField from '../src/templates/generate-prompt-input-fields.js';
|
|
4
|
-
import changeCase from
|
|
4
|
+
import * as changeCase from "change-case";
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
7
|
camelCase,
|
|
@@ -95,7 +95,7 @@ describe('should create all necessary prompt input initial, hint and validate fi
|
|
|
95
95
|
|
|
96
96
|
it('it should generate initial value for directory based on app path', ()=>{
|
|
97
97
|
let inputType = 'fileDirectory';
|
|
98
|
-
const initialViewStreamFileDir = new PromptInputField(inputType, fileType,
|
|
98
|
+
const initialViewStreamFileDir = new PromptInputField(inputType, fileType, 'initial').field;
|
|
99
99
|
const initialDomElementFileDir = new PromptInputField(inputType, 'DomElement', 'initial').field;
|
|
100
100
|
const initialChannelFileDir = new PromptInputField(inputType, 'Channel', 'initial').field;
|
|
101
101
|
const initialSpyneTraitFileDir = new PromptInputField(inputType, 'SpyneTrait', 'initial').field;
|
|
@@ -107,6 +107,7 @@ describe('should create all necessary prompt input initial, hint and validate fi
|
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
//console.log('process ',{initialFileDir})
|
|
110
|
+
|
|
110
111
|
expect(initialViewStreamFileDir).to.equal('./src/app/components/')
|
|
111
112
|
expect(initialDomElementFileDir).to.equal('./src/app/components/')
|
|
112
113
|
expect(initialChannelFileDir).to.equal('./src/app/channels/')
|
|
@@ -6,7 +6,7 @@ const {answersArr} = AnswersData;
|
|
|
6
6
|
import {generatePromptOutput} from '../src/templates/generate-prompt-output.js';
|
|
7
7
|
import * as R from 'ramda';
|
|
8
8
|
|
|
9
|
-
const getAnswersByFileType = (fileType) => R.compose(R.head, R.filter(R.propEq(
|
|
9
|
+
const getAnswersByFileType = (fileType) => R.compose(R.head, R.filter(R.propEq(fileType, 'fileType')))(answersArr)
|
|
10
10
|
|
|
11
11
|
const defaultStr = `
|
|
12
12
|
import {Subject} from 'rxjs';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Some content for tests/utils/file-utils-test -- should test saving to file
|
|
@@ -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
|