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 +105 -3
- package/index.js +18 -11
- package/mocha.conf.cjs +16 -0
- package/package.json +20 -15
- package/src/spyne-app-create.js +209 -0
- package/src/spyne-app-creator.js +2 -2
- package/src/spyne-file-prompt.js +16 -2
- package/src/spyne-starter-app-create.js +76 -0
- 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/create-spyne-app.test.js +2 -3
- package/tests/generate-file-prompt.test.js +1 -3
- package/tests/generate-file-string.test.js +1 -3
- package/tests/generate-prompt-input-fields-methods.test.js +4 -5
- package/tests/generate-prompt-input-fields.test.js +1 -3
- package/tests/generate-prompt-input-object.test.js +1 -3
- package/tests/generate-prompt-output.test.js +2 -4
- package/tests/index.test.js +1 -3
- package/tests/utils/file-utils.test.js +2 -4
- 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
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import { SpyneCliUI } from './src/ui.js';
|
|
3
4
|
import clear from 'clear';
|
|
4
|
-
import
|
|
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
|
-
|
|
10
|
-
const
|
|
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 (
|
|
20
|
-
|
|
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.
|
|
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": "
|
|
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.
|
|
27
|
-
"boxen": "^
|
|
28
|
-
"chalk": "^5.
|
|
29
|
-
"change-case": "^4.
|
|
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.
|
|
32
|
-
"figlet": "^1.
|
|
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
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
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": "^
|
|
41
|
-
"mocha": "^
|
|
42
|
-
"nodemon": "^
|
|
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;
|
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
|
|
|
@@ -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
|
|
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.
|
|
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" :
|
|
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
|
+
};
|
package/src/utils/file-utils.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
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,6 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
|
2
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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';
|
package/tests/index.test.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
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(
|
|
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
|