create-manifest 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/assets/README.md +49 -0
- package/assets/backend.yml +22 -0
- package/assets/default-package.json +7 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/index.d.ts +62 -0
- package/dist/commands/index.js +318 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/GetBackendFileContent.d.ts +1 -0
- package/dist/utils/GetBackendFileContent.js +21 -0
- package/dist/utils/GetLatestPackageVersion.d.ts +1 -0
- package/dist/utils/GetLatestPackageVersion.js +5 -0
- package/dist/utils/UpdateExtensionJsonFile.d.ts +6 -0
- package/dist/utils/UpdateExtensionJsonFile.js +8 -0
- package/dist/utils/UpdatePackageJsonFile.d.ts +18 -0
- package/dist/utils/UpdatePackageJsonFile.js +21 -0
- package/dist/utils/UpdateSettingsJsonFile.d.ts +4 -0
- package/dist/utils/UpdateSettingsJsonFile.js +6 -0
- package/oclif.manifest.json +4 -0
- package/package.json +84 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# create-manifest
|
|
2
|
+
|
|
3
|
+
The `create-manifest` command adds [Manifest](https://manifest.build) to your project.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx create-manifest@latest
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Develop
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install
|
|
13
|
+
|
|
14
|
+
# Run from a test folder to prevent messing with project files.
|
|
15
|
+
mkdir test-folder
|
|
16
|
+
cd test-folder
|
|
17
|
+
../bin/dev.js
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
However due to the monorepo workspace structure, the launch script will fail as the path to the node modules folder is different than when served. This is normal.
|
|
21
|
+
|
|
22
|
+
## Publish
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm run build
|
|
26
|
+
npm publish
|
|
27
|
+
```
|
package/assets/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<br>
|
|
2
|
+
<p align="center">
|
|
3
|
+
<a href="https://manifest.build/#gh-light-mode-only">
|
|
4
|
+
<img alt="manifest" src="https://manifest.build/assets/images/logo-transparent.svg" height="55px" alt="Manifest logo" title="Manifest - A backend so simple that it fits in a YAML file" />
|
|
5
|
+
</a>
|
|
6
|
+
<a href="https://manifest.build/#gh-dark-mode-only">
|
|
7
|
+
<img alt="manifest" src="https://manifest.build/assets/images/logo-light.svg" height="55px" alt="Manifest logo" title="Manifest - A backend so simple that it fits in a YAML file" />
|
|
8
|
+
</a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align='center'>
|
|
12
|
+
<strong>A backend so simple that it fits into 1 YAML file</strong>
|
|
13
|
+
|
|
14
|
+
## Description
|
|
15
|
+
|
|
16
|
+
Welcome to your [Manifest](https://github.com/mnfst/manifest) project ! Feel free to replace this README by your own.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
$ npm install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Running the app
|
|
25
|
+
|
|
26
|
+
To run the app in the development mode:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm run manifest
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- Open [http://localhost:1111](http://localhost:1111) to open your admin UI it in your browser
|
|
33
|
+
- Open [http://localhost:1111/api](http://localhost:111/api) to view your REST API documentation
|
|
34
|
+
|
|
35
|
+
The page will reload when you make changes.
|
|
36
|
+
|
|
37
|
+
## Seed dummy data
|
|
38
|
+
|
|
39
|
+
Seeds some dummy data for your entities:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run manifest:seed
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Community & Resources
|
|
46
|
+
|
|
47
|
+
- [Docs](https://manifest.build/docs) - Get started with Manifest
|
|
48
|
+
- [Discord](https://discord.gg/FepAked3W7) - Come chat with the community
|
|
49
|
+
- [Github](https://github.com/mnfst/manifest/issues) - Report bugs and share ideas to improve the product.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: My pet app 🐾
|
|
2
|
+
entities:
|
|
3
|
+
Owner:
|
|
4
|
+
properties:
|
|
5
|
+
- name
|
|
6
|
+
- { name: birthdate, type: date }
|
|
7
|
+
|
|
8
|
+
Cat:
|
|
9
|
+
properties:
|
|
10
|
+
- name
|
|
11
|
+
- { name: age, type: number }
|
|
12
|
+
- { name: birthdate, type: date }
|
|
13
|
+
belongsTo:
|
|
14
|
+
- Owner
|
|
15
|
+
|
|
16
|
+
Homepage:
|
|
17
|
+
nameSingular: Home content
|
|
18
|
+
single: true
|
|
19
|
+
properties:
|
|
20
|
+
- title
|
|
21
|
+
- { name: description, type: richText }
|
|
22
|
+
- { name: cover, type: image }
|
package/bin/dev.cmd
ADDED
package/bin/dev.js
ADDED
package/bin/run.cmd
ADDED
package/bin/run.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export declare class MyCommand extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
firstArg: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static flags: {
|
|
7
|
+
backendFile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
cursor: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The run method is called when the command is run.
|
|
12
|
+
*
|
|
13
|
+
* Steps:
|
|
14
|
+
* 1. Create a folder named after the first arg or ask for it
|
|
15
|
+
* 2. Create a folder with the name `manifest`.
|
|
16
|
+
* 3. Create a file inside the folder with the name `manifest.yml`.
|
|
17
|
+
* 4. Update the `package.json` file with the new packages and scripts.
|
|
18
|
+
* 5. Update the .vscode/extensions.json file with the recommended extensions.
|
|
19
|
+
* 6. Update the .vscode/settings.json file with the recommended settings.
|
|
20
|
+
* 7. Update the .gitignore file with the recommended settings.
|
|
21
|
+
* 8. Update the .env file with the environment variables.
|
|
22
|
+
* 9. If no README.md file exists, create one.
|
|
23
|
+
* 10. Add optional files based on flags
|
|
24
|
+
* 11. Install the new packages.
|
|
25
|
+
* 12. Serve the new app.
|
|
26
|
+
* 13. Wait for the server to start.
|
|
27
|
+
* 14. Seed the database.
|
|
28
|
+
* 15. Open the browser.
|
|
29
|
+
*/
|
|
30
|
+
run(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Check if the server is ready.
|
|
33
|
+
*
|
|
34
|
+
* @returns {Promise<boolean>} - Returns a promise that resolves to a boolean.
|
|
35
|
+
*
|
|
36
|
+
**/
|
|
37
|
+
isServerReady(): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Wait for the server to be ready.
|
|
40
|
+
*
|
|
41
|
+
* @returns {Promise<void>} - Returns a promise that resolves to void when the server is ready.
|
|
42
|
+
*
|
|
43
|
+
**/
|
|
44
|
+
waitForServerToBeReady(): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Transform a JSON with comments to a JSON without comments.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} jsonWithComments - The JSON with comments.
|
|
49
|
+
*
|
|
50
|
+
* @returns {string} - The JSON without comments.
|
|
51
|
+
*
|
|
52
|
+
**/
|
|
53
|
+
removeComments(jsonString: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Kill a process without logging an error if it fails.
|
|
56
|
+
*
|
|
57
|
+
* @param {number} pid - The process ID.
|
|
58
|
+
* @returns {Promise<void>} - A promise that resolves when the process is killed.
|
|
59
|
+
*
|
|
60
|
+
*/
|
|
61
|
+
silentKill(pid: number): Promise<void>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { exec as execCp } from 'node:child_process';
|
|
4
|
+
import * as crypto from 'node:crypto';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import treeKill from 'tree-kill';
|
|
11
|
+
import { parse } from 'jsonc-parser';
|
|
12
|
+
import { updateExtensionJsonFile } from '../utils/UpdateExtensionJsonFile.js';
|
|
13
|
+
import { updatePackageJsonFile } from '../utils/UpdatePackageJsonFile.js';
|
|
14
|
+
import { updateSettingsJsonFile } from '../utils/UpdateSettingsJsonFile.js';
|
|
15
|
+
import { getLatestPackageVersion } from '../utils/GetLatestPackageVersion.js';
|
|
16
|
+
import { getBackendFileContent } from '../utils/GetBackendFileContent.js';
|
|
17
|
+
import { input } from '@inquirer/prompts';
|
|
18
|
+
const exec = promisify(execCp);
|
|
19
|
+
export class MyCommand extends Command {
|
|
20
|
+
static args = {
|
|
21
|
+
firstArg: Args.string({
|
|
22
|
+
name: 'name',
|
|
23
|
+
description: 'The name for the new workspace and the initial project. It will be used for the root directory.'
|
|
24
|
+
})
|
|
25
|
+
};
|
|
26
|
+
static flags = {
|
|
27
|
+
backendFile: Flags.string({
|
|
28
|
+
summary: 'The remote file to use as a template for the backend.yml file. If not provided, the default file will be used.'
|
|
29
|
+
}),
|
|
30
|
+
cursor: Flags.boolean()
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The run method is called when the command is run.
|
|
34
|
+
*
|
|
35
|
+
* Steps:
|
|
36
|
+
* 1. Create a folder named after the first arg or ask for it
|
|
37
|
+
* 2. Create a folder with the name `manifest`.
|
|
38
|
+
* 3. Create a file inside the folder with the name `manifest.yml`.
|
|
39
|
+
* 4. Update the `package.json` file with the new packages and scripts.
|
|
40
|
+
* 5. Update the .vscode/extensions.json file with the recommended extensions.
|
|
41
|
+
* 6. Update the .vscode/settings.json file with the recommended settings.
|
|
42
|
+
* 7. Update the .gitignore file with the recommended settings.
|
|
43
|
+
* 8. Update the .env file with the environment variables.
|
|
44
|
+
* 9. If no README.md file exists, create one.
|
|
45
|
+
* 10. Add optional files based on flags
|
|
46
|
+
* 11. Install the new packages.
|
|
47
|
+
* 12. Serve the new app.
|
|
48
|
+
* 13. Wait for the server to start.
|
|
49
|
+
* 14. Seed the database.
|
|
50
|
+
* 15. Open the browser.
|
|
51
|
+
*/
|
|
52
|
+
async run() {
|
|
53
|
+
// * 1 Create a folder named after the first argument or ask for it.
|
|
54
|
+
const { argv } = await this.parse(MyCommand);
|
|
55
|
+
let projectName = argv[0];
|
|
56
|
+
if (!projectName) {
|
|
57
|
+
projectName = await input({
|
|
58
|
+
message: 'What name would you like to use for the new workspace?',
|
|
59
|
+
validate: (input) => {
|
|
60
|
+
if (!input.trim()) {
|
|
61
|
+
return 'The name name cannot be empty';
|
|
62
|
+
}
|
|
63
|
+
// Check for invalid characters in The name names
|
|
64
|
+
if (/[<>:"/\\|?*]/.test(input)) {
|
|
65
|
+
return 'Folder name contains invalid characters';
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const spinner = ora(`Creating your Manifest project in ${projectName} folder...`).start();
|
|
72
|
+
const projectFolderPath = path.join(process.cwd(), projectName);
|
|
73
|
+
// Check if the folder already exists
|
|
74
|
+
if (fs.existsSync(projectFolderPath)) {
|
|
75
|
+
spinner.fail(`Error: The "${projectFolderPath}" folder already exists in the current directory. Please find another name.`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
fs.mkdirSync(projectFolderPath);
|
|
79
|
+
const manifestFolderName = 'manifest';
|
|
80
|
+
const initialFileName = 'backend.yml';
|
|
81
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
82
|
+
const assetFolderPath = path.join(__dirname, '..', '..', 'assets');
|
|
83
|
+
// * 2. Create a folder with the name `manifest`.
|
|
84
|
+
// Construct the folder path. This example creates the folder in the current working directory.
|
|
85
|
+
const manifestFolderPath = path.join(projectFolderPath, manifestFolderName);
|
|
86
|
+
// Create the folder
|
|
87
|
+
fs.mkdirSync(manifestFolderPath);
|
|
88
|
+
// * 3. Create a file inside the folder with the name `manifest.yml`.
|
|
89
|
+
// Path where the new file should be created
|
|
90
|
+
const newFilePath = path.join(manifestFolderPath, initialFileName);
|
|
91
|
+
// Get the content of the file either remote or local.
|
|
92
|
+
const { flags } = await this.parse(MyCommand);
|
|
93
|
+
const remoteBackendFile = flags.backendFile;
|
|
94
|
+
const content = await getBackendFileContent(path.join(assetFolderPath, initialFileName), remoteBackendFile);
|
|
95
|
+
// Write the content to the new file
|
|
96
|
+
fs.writeFileSync(newFilePath, content);
|
|
97
|
+
spinner.succeed();
|
|
98
|
+
spinner.start('Update package.json file...');
|
|
99
|
+
// Update package.json file.
|
|
100
|
+
const packagePath = path.join(projectFolderPath, 'package.json');
|
|
101
|
+
let packageJson;
|
|
102
|
+
if (fs.existsSync(packagePath)) {
|
|
103
|
+
packageJson = parse(fs.readFileSync(packagePath, 'utf8'));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
packageJson = JSON.parse(fs.readFileSync(path.join(assetFolderPath, 'default-package.json'), 'utf8'));
|
|
107
|
+
}
|
|
108
|
+
const manifestLatestVersion = await getLatestPackageVersion('manifest');
|
|
109
|
+
fs.writeFileSync(packagePath, updatePackageJsonFile({
|
|
110
|
+
fileContent: packageJson,
|
|
111
|
+
newPackages: {
|
|
112
|
+
manifest: `^${manifestLatestVersion}`
|
|
113
|
+
},
|
|
114
|
+
newScripts: {
|
|
115
|
+
manifest: 'node node_modules/manifest/scripts/watch/watch.js',
|
|
116
|
+
'manifest:seed': 'node node_modules/manifest/dist/manifest/src/seed/scripts/seed.js'
|
|
117
|
+
}
|
|
118
|
+
}));
|
|
119
|
+
spinner.succeed();
|
|
120
|
+
spinner.start('Add settings...');
|
|
121
|
+
// Update .vscode/extensions.json file.
|
|
122
|
+
const vscodeDirPath = path.join(projectFolderPath, '.vscode');
|
|
123
|
+
const extensionsFilePath = path.join(vscodeDirPath, 'extensions.json');
|
|
124
|
+
let extensionsJson;
|
|
125
|
+
// Ensure the `.vscode` Directory Exists
|
|
126
|
+
if (!fs.existsSync(vscodeDirPath)) {
|
|
127
|
+
fs.mkdirSync(vscodeDirPath);
|
|
128
|
+
}
|
|
129
|
+
// Read or Initialize `extensions.json`
|
|
130
|
+
if (fs.existsSync(extensionsFilePath)) {
|
|
131
|
+
extensionsJson = parse(fs.readFileSync(extensionsFilePath, 'utf8'));
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
extensionsJson = { recommendations: [] };
|
|
135
|
+
}
|
|
136
|
+
fs.writeFileSync(extensionsFilePath, updateExtensionJsonFile({
|
|
137
|
+
extensions: ['redhat.vscode-yaml'],
|
|
138
|
+
fileContent: extensionsJson
|
|
139
|
+
}));
|
|
140
|
+
// Update .vscode/extensions.json file.
|
|
141
|
+
const settingsFilePath = path.join(vscodeDirPath, 'settings.json');
|
|
142
|
+
let settingsJson;
|
|
143
|
+
// Read or Initialize `settings.json`
|
|
144
|
+
if (fs.existsSync(settingsFilePath)) {
|
|
145
|
+
settingsJson = parse(fs.readFileSync(settingsFilePath, 'utf8'));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
settingsJson = {};
|
|
149
|
+
}
|
|
150
|
+
fs.writeFileSync(settingsFilePath, updateSettingsJsonFile({
|
|
151
|
+
fileContent: settingsJson,
|
|
152
|
+
settings: {
|
|
153
|
+
'yaml.schemas': {
|
|
154
|
+
'https://schema.manifest.build/schema.json': '**/manifest/**.yml'
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}));
|
|
158
|
+
// * 7. Update the .env file with the environment variables.
|
|
159
|
+
const gitignorePath = path.join(projectFolderPath, '.gitignore');
|
|
160
|
+
let gitignoreContent = '';
|
|
161
|
+
if (fs.existsSync(gitignorePath)) {
|
|
162
|
+
gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
163
|
+
}
|
|
164
|
+
const newGitignoreLines = [
|
|
165
|
+
'node_modules',
|
|
166
|
+
'.env',
|
|
167
|
+
'public',
|
|
168
|
+
'manifest/backend.db'
|
|
169
|
+
];
|
|
170
|
+
newGitignoreLines.forEach((line) => {
|
|
171
|
+
if (!gitignoreContent.includes(line)) {
|
|
172
|
+
gitignoreContent += `\n${line}`;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
176
|
+
spinner.succeed();
|
|
177
|
+
// * 9. Add a README.md file if it doesn't exist.
|
|
178
|
+
const readmeFilePath = path.join(projectFolderPath, 'README.md');
|
|
179
|
+
if (!fs.existsSync(readmeFilePath)) {
|
|
180
|
+
fs.writeFileSync(readmeFilePath, fs.readFileSync(path.join(assetFolderPath, 'README.md'), 'utf8'));
|
|
181
|
+
}
|
|
182
|
+
// * 10. Add optional files based on flags
|
|
183
|
+
// Add rules for Cursor IDE.
|
|
184
|
+
if (flags.cursor) {
|
|
185
|
+
spinner.start('Add rules for Cursor IDE...');
|
|
186
|
+
const cursorFolderPath = path.join(projectFolderPath, '.cursor', 'rules');
|
|
187
|
+
const cursorFileName = 'manifest.mdc';
|
|
188
|
+
fs.mkdirSync(cursorFolderPath, { recursive: true });
|
|
189
|
+
let cursorFileContent;
|
|
190
|
+
try {
|
|
191
|
+
const response = await fetch('https://raw.githubusercontent.com/mnfst/rules/refs/heads/main/cursor/manifest.mdc');
|
|
192
|
+
if (!response.ok) {
|
|
193
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
194
|
+
}
|
|
195
|
+
cursorFileContent = await response.text();
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
console.error('Error fetching YAML:', error);
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
// Write the content to the new file
|
|
202
|
+
fs.writeFileSync(path.join(cursorFolderPath, cursorFileName), cursorFileContent);
|
|
203
|
+
spinner.succeed();
|
|
204
|
+
}
|
|
205
|
+
// * 9. Install the new packages.
|
|
206
|
+
spinner.start('Install dependencies...');
|
|
207
|
+
// Install deps.
|
|
208
|
+
try {
|
|
209
|
+
await exec('npm install');
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
spinner.fail(`Execution error: ${error}`);
|
|
213
|
+
}
|
|
214
|
+
// Serve the new app.
|
|
215
|
+
spinner.succeed();
|
|
216
|
+
spinner.start('Add environment variables...');
|
|
217
|
+
// Add environment variables to .env file
|
|
218
|
+
const envFilePath = path.join(projectFolderPath, '.env');
|
|
219
|
+
const envJWTSecret = `TOKEN_SECRET_KEY=${crypto
|
|
220
|
+
.randomBytes(32)
|
|
221
|
+
.toString('hex')}`;
|
|
222
|
+
let envContent;
|
|
223
|
+
if (fs.existsSync(envFilePath)) {
|
|
224
|
+
envContent = fs.readFileSync(envFilePath, 'utf8');
|
|
225
|
+
envContent += `\n` + envJWTSecret;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
envContent = envJWTSecret;
|
|
229
|
+
}
|
|
230
|
+
fs.writeFileSync(envFilePath, envContent);
|
|
231
|
+
spinner.succeed();
|
|
232
|
+
spinner.start('Build the database...');
|
|
233
|
+
let serveTask = null;
|
|
234
|
+
try {
|
|
235
|
+
// We run the manifest script to build the database.
|
|
236
|
+
serveTask = exec('npm run manifest');
|
|
237
|
+
await this.waitForServerToBeReady();
|
|
238
|
+
spinner.succeed();
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
spinner.fail(`Execution error: ${error}`);
|
|
242
|
+
}
|
|
243
|
+
spinner.start('Seed initial data...');
|
|
244
|
+
try {
|
|
245
|
+
await exec('npm run manifest:seed');
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
spinner.fail(`Execution error: ${error}`);
|
|
249
|
+
}
|
|
250
|
+
spinner.succeed();
|
|
251
|
+
console.log();
|
|
252
|
+
console.log('🎉 Manifest successfully installed !');
|
|
253
|
+
console.log();
|
|
254
|
+
console.log('🚀 Run `npm run manifest` to start the server.');
|
|
255
|
+
console.log();
|
|
256
|
+
await this.silentKill(serveTask?.child?.pid || 0);
|
|
257
|
+
process.exit();
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Check if the server is ready.
|
|
261
|
+
*
|
|
262
|
+
* @returns {Promise<boolean>} - Returns a promise that resolves to a boolean.
|
|
263
|
+
*
|
|
264
|
+
**/
|
|
265
|
+
async isServerReady() {
|
|
266
|
+
return axios
|
|
267
|
+
.get('http://localhost:1111/api/health')
|
|
268
|
+
.then(() => true)
|
|
269
|
+
.catch(() => false);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Wait for the server to be ready.
|
|
273
|
+
*
|
|
274
|
+
* @returns {Promise<void>} - Returns a promise that resolves to void when the server is ready.
|
|
275
|
+
*
|
|
276
|
+
**/
|
|
277
|
+
async waitForServerToBeReady() {
|
|
278
|
+
let serverReady = false;
|
|
279
|
+
while (!serverReady) {
|
|
280
|
+
serverReady = await this.isServerReady();
|
|
281
|
+
if (!serverReady) {
|
|
282
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1s before retrying
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Transform a JSON with comments to a JSON without comments.
|
|
288
|
+
*
|
|
289
|
+
* @param {string} jsonWithComments - The JSON with comments.
|
|
290
|
+
*
|
|
291
|
+
* @returns {string} - The JSON without comments.
|
|
292
|
+
*
|
|
293
|
+
**/
|
|
294
|
+
removeComments(jsonString) {
|
|
295
|
+
return jsonString
|
|
296
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
|
|
297
|
+
.replace(/\/\/.*$/gm, ''); // Remove single-line comments
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Kill a process without logging an error if it fails.
|
|
301
|
+
*
|
|
302
|
+
* @param {number} pid - The process ID.
|
|
303
|
+
* @returns {Promise<void>} - A promise that resolves when the process is killed.
|
|
304
|
+
*
|
|
305
|
+
*/
|
|
306
|
+
silentKill(pid) {
|
|
307
|
+
return new Promise((resolve, reject) => {
|
|
308
|
+
treeKill(pid, 'SIGKILL', (err) => {
|
|
309
|
+
if (err) {
|
|
310
|
+
reject(`Failed to kill process: ${err}`);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
resolve();
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getBackendFileContent: (localPath: string, remotePath?: string) => Promise<string>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
export const getBackendFileContent = async (localPath, remotePath) => {
|
|
3
|
+
// We use local default example backend file if remotePath is not provided.
|
|
4
|
+
if (!remotePath) {
|
|
5
|
+
return Promise.resolve(fs.readFileSync(localPath, 'utf8'));
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch(remotePath);
|
|
10
|
+
if (!response.ok) {
|
|
11
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
12
|
+
}
|
|
13
|
+
const yamlContent = await response.text();
|
|
14
|
+
return yamlContent;
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
console.error('Error fetching YAML:', error);
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getLatestPackageVersion: (packageName: string) => Promise<string>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const updateExtensionJsonFile = ({ extensions, fileContent }) => {
|
|
2
|
+
extensions.forEach((extension) => {
|
|
3
|
+
if (!fileContent.recommendations.includes(extension)) {
|
|
4
|
+
fileContent.recommendations.push(extension);
|
|
5
|
+
}
|
|
6
|
+
});
|
|
7
|
+
return JSON.stringify(fileContent, null, 2);
|
|
8
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Updates the package.json file with new packages and scripts.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} params - The parameters for updating the package.json file.
|
|
5
|
+
* @param {JSON} params.fileContent - The current content of the package.json file.
|
|
6
|
+
* @param {Record<string, string>} params.newPackages - An object where the keys are the names of the new packages and the values are the versions.
|
|
7
|
+
* @param {Record<string, string>} params.newScripts - An object where the keys are the names of the new scripts and the values are the script commands.
|
|
8
|
+
*
|
|
9
|
+
* @returns {string} The updated content of the package.json file.
|
|
10
|
+
*/
|
|
11
|
+
export declare const updatePackageJsonFile: ({ fileContent, newPackages, newScripts }: {
|
|
12
|
+
fileContent: {
|
|
13
|
+
scripts: Record<string, string>;
|
|
14
|
+
dependencies: Record<string, string>;
|
|
15
|
+
};
|
|
16
|
+
newPackages: Record<string, string>;
|
|
17
|
+
newScripts: Record<string, string>;
|
|
18
|
+
}) => string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Updates the package.json file with new packages and scripts.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} params - The parameters for updating the package.json file.
|
|
5
|
+
* @param {JSON} params.fileContent - The current content of the package.json file.
|
|
6
|
+
* @param {Record<string, string>} params.newPackages - An object where the keys are the names of the new packages and the values are the versions.
|
|
7
|
+
* @param {Record<string, string>} params.newScripts - An object where the keys are the names of the new scripts and the values are the script commands.
|
|
8
|
+
*
|
|
9
|
+
* @returns {string} The updated content of the package.json file.
|
|
10
|
+
*/
|
|
11
|
+
export const updatePackageJsonFile = ({ fileContent, newPackages, newScripts }) => {
|
|
12
|
+
fileContent.scripts = {
|
|
13
|
+
...fileContent.scripts,
|
|
14
|
+
...newScripts
|
|
15
|
+
};
|
|
16
|
+
fileContent.dependencies = {
|
|
17
|
+
...fileContent.dependencies,
|
|
18
|
+
...newPackages
|
|
19
|
+
};
|
|
20
|
+
return JSON.stringify(fileContent, null, 2);
|
|
21
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-manifest",
|
|
3
|
+
"version": "1.1.3",
|
|
4
|
+
"author": "Manifest",
|
|
5
|
+
"description": "Create a new Manifest backend",
|
|
6
|
+
"homepage": "https://manifest.build",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"bin": {
|
|
9
|
+
"create-manifest": "./bin/run.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "shx rm -rf dist && tsc -b",
|
|
13
|
+
"watch": "tsc -b --watch",
|
|
14
|
+
"postpack": "shx rm -f oclif.manifest.json",
|
|
15
|
+
"posttest": "npm run lint",
|
|
16
|
+
"prepack": "npm run build && oclif manifest && oclif readme",
|
|
17
|
+
"prepare": "npm run build",
|
|
18
|
+
"version": "oclif readme && git add README.md"
|
|
19
|
+
},
|
|
20
|
+
"oclif": {
|
|
21
|
+
"bin": "create-manifest",
|
|
22
|
+
"dirname": "create-manifest",
|
|
23
|
+
"strategy": "single",
|
|
24
|
+
"target": "./dist/commands/index.js"
|
|
25
|
+
},
|
|
26
|
+
"plugins": [
|
|
27
|
+
"@oclif/plugin-help",
|
|
28
|
+
"@oclif/plugin-plugins"
|
|
29
|
+
],
|
|
30
|
+
"topicSeparator": " ",
|
|
31
|
+
"topics": {
|
|
32
|
+
"create": {
|
|
33
|
+
"description": "Create a new Manifest backend"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@inquirer/prompts": "^7.5.1",
|
|
38
|
+
"@oclif/core": "^4",
|
|
39
|
+
"@oclif/plugin-help": "^6",
|
|
40
|
+
"@oclif/plugin-plugins": "^5",
|
|
41
|
+
"axios": "^1.9.0",
|
|
42
|
+
"chalk": "^5.4.1",
|
|
43
|
+
"fs": "^0.0.1-security",
|
|
44
|
+
"jsonc-parser": "^3.3.1",
|
|
45
|
+
"ora": "^8.2.0",
|
|
46
|
+
"path": "^0.12.7",
|
|
47
|
+
"tree-kill": "^1.2.2",
|
|
48
|
+
"url": "^0.11.4"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@oclif/prettier-config": "^0.2.1",
|
|
52
|
+
"@oclif/test": "^4",
|
|
53
|
+
"oclif": "^4.17.46",
|
|
54
|
+
"shx": "^0.4.0",
|
|
55
|
+
"ts-node": "^10.9.2",
|
|
56
|
+
"typescript": "^5"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0"
|
|
60
|
+
},
|
|
61
|
+
"files": [
|
|
62
|
+
"/bin",
|
|
63
|
+
"/dist",
|
|
64
|
+
"/assets",
|
|
65
|
+
"/oclif.manifest.json"
|
|
66
|
+
],
|
|
67
|
+
"main": "",
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git+https://github.com/mnfst/manifest.git"
|
|
71
|
+
},
|
|
72
|
+
"bugs": "https://github.com/mnfst/manifest/issues",
|
|
73
|
+
"keywords": [
|
|
74
|
+
"manifest",
|
|
75
|
+
"micro-backend",
|
|
76
|
+
"backend",
|
|
77
|
+
"headless",
|
|
78
|
+
"install",
|
|
79
|
+
"rest"
|
|
80
|
+
],
|
|
81
|
+
"types": "dist/index.d.ts",
|
|
82
|
+
"exports": "./lib/index.js",
|
|
83
|
+
"type": "module"
|
|
84
|
+
}
|