@appliance.sh/cli 1.8.0 → 1.10.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/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@appliance.sh/cli",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Shell for installing, running, and developing applications on the Appliance platform",
5
5
  "repository": "https://github.com/appliance-sh/appliance.sh",
6
6
  "license": "MIT",
7
7
  "author": "Eliot Lim",
8
- "type": "commonjs",
8
+ "type": "module",
9
9
  "main": "index.js",
10
10
  "bin": {
11
11
  "appliance": "dist/appliance.js"
@@ -17,11 +17,15 @@
17
17
  "test": "echo \"Error: no test specified\" && exit 1"
18
18
  },
19
19
  "dependencies": {
20
- "@appliance.sh/sdk": "1.8.0",
20
+ "@appliance.sh/sdk": "1.10.0",
21
+ "@inquirer/prompts": "^7.10.1",
21
22
  "chalk": "^5.4.1",
22
- "commander": "^14.0.0"
23
+ "commander": "^14.0.0",
24
+ "json-diff": "^1.0.6",
25
+ "random-word-slugs": "^0.1.7"
23
26
  },
24
27
  "devDependencies": {
28
+ "@types/json-diff": "^1.0.3",
25
29
  "@types/node": "^24.0.1"
26
30
  }
27
31
  }
@@ -1,4 +1,15 @@
1
1
  import { Command } from 'commander';
2
+ import { extractApplianceFile, saveApplianceFile } from './utils/common.js';
3
+ import {
4
+ promptForApplianceFramework,
5
+ promptForApplianceName,
6
+ promptForAppliancePort,
7
+ promptForApplianceType,
8
+ } from './wizards/appliance.js';
9
+ import * as diff from 'json-diff';
10
+
11
+ import * as prompt from '@inquirer/prompts';
12
+ import { ApplianceInput, ApplianceType } from '@appliance.sh/sdk';
2
13
 
3
14
  const program = new Command();
4
15
 
@@ -6,4 +17,71 @@ program
6
17
  .option('-f, --file <file>', 'appliance manifest file', 'appliance.json')
7
18
  .option('-d, --directory <directory>', 'appliance directory');
8
19
 
9
- program.parse(process.argv);
20
+ const cmd = program.parse(process.argv);
21
+
22
+ const applianceFileResult = extractApplianceFile(cmd);
23
+
24
+ // If a file or directory was specified, but the file was not found, exit with an error.
25
+ if (
26
+ ((cmd.getOptionValue('file') && cmd.getOptionValue('file') !== 'appliance.json') ||
27
+ cmd.getOptionValue('directory')) &&
28
+ !applianceFileResult.success
29
+ ) {
30
+ console.log('The specified file was not found.');
31
+ process.exit(1);
32
+ }
33
+
34
+ if (!applianceFileResult.success) {
35
+ console.log(`An error occurred while reading the specified file.`);
36
+ console.log(applianceFileResult.error);
37
+ }
38
+
39
+ let updatedApplianceFile = {
40
+ manifest: 'v1',
41
+ ...applianceFileResult.data,
42
+ } as ApplianceInput;
43
+
44
+ try {
45
+ const name = await promptForApplianceName(updatedApplianceFile);
46
+
47
+ updatedApplianceFile = {
48
+ ...updatedApplianceFile,
49
+ name,
50
+ };
51
+
52
+ const type = await promptForApplianceType(updatedApplianceFile);
53
+
54
+ if (type === ApplianceType.framework) {
55
+ const framework = await promptForApplianceFramework(updatedApplianceFile);
56
+ updatedApplianceFile = {
57
+ ...updatedApplianceFile,
58
+ type,
59
+ framework,
60
+ };
61
+ } else if (type === ApplianceType.container) {
62
+ const port = await promptForAppliancePort(updatedApplianceFile);
63
+
64
+ updatedApplianceFile = {
65
+ ...updatedApplianceFile,
66
+ type,
67
+ port,
68
+ };
69
+ } else {
70
+ updatedApplianceFile = {
71
+ ...updatedApplianceFile,
72
+ type,
73
+ };
74
+ }
75
+
76
+ console.log(diff.colorize(diff.diff(applianceFileResult.data, updatedApplianceFile)));
77
+
78
+ const saveChanges = await prompt.confirm({ message: 'Save changes?', default: true });
79
+ if (!saveChanges) {
80
+ console.log('Changes cancelled.');
81
+ process.exit(0);
82
+ }
83
+
84
+ saveApplianceFile(cmd.getOptionValue('file') || 'appliance.json', updatedApplianceFile);
85
+ } catch (error) {
86
+ console.error(error);
87
+ }
package/src/appliance.ts CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  import { Command } from 'commander';
4
4
 
5
- import { VERSION } from '@appliance.sh/sdk';
5
+ import * as sdk from '@appliance.sh/sdk';
6
6
 
7
7
  const program = new Command();
8
8
 
9
9
  program
10
10
  .name('appliance')
11
- .version(VERSION)
11
+ .version(sdk.VERSION)
12
12
  .command('build', 'builds the appliance in the current working directory')
13
13
  .command('configure', 'configures the appliance in the current working directory')
14
14
  .command('install [appliance-names...]', 'install one or more appliances')
@@ -0,0 +1,51 @@
1
+ import { Command } from 'commander';
2
+ import path from 'path';
3
+ import * as fs from 'node:fs';
4
+ import { Appliance, applianceInput, ApplianceInput, Result } from '@appliance.sh/sdk';
5
+
6
+ export function extractApplianceFile(cmd: Command): Result<ApplianceInput> {
7
+ let filePath;
8
+ if (cmd.getOptionValue('file')) {
9
+ filePath = path.resolve(process.cwd(), cmd.getOptionValue('file'));
10
+ } else if (cmd.getOptionValue('directory')) {
11
+ filePath = path.resolve(process.cwd(), cmd.getOptionValue('directory'), 'appliance.json');
12
+ }
13
+
14
+ // check if file exists
15
+ if (!filePath) {
16
+ return {
17
+ success: false,
18
+ error: { name: 'File Not Found', message: 'No appliance file found.' },
19
+ };
20
+ }
21
+
22
+ if (!filePath.endsWith('.json')) {
23
+ throw new Error('Appliance file must be a JSON file');
24
+ }
25
+
26
+ try {
27
+ const fileBuf = fs.readFileSync(filePath);
28
+ const result = applianceInput.safeParse(JSON.parse(fileBuf.toString()));
29
+
30
+ return result;
31
+ } catch (err) {
32
+ return {
33
+ success: false,
34
+ error: err as Error,
35
+ };
36
+ }
37
+ }
38
+
39
+ export function saveApplianceFile(filePath: string, appliance: Appliance) {
40
+ try {
41
+ fs.writeFileSync(filePath, JSON.stringify(appliance, null, 2));
42
+ return {
43
+ success: true,
44
+ };
45
+ } catch (err) {
46
+ return {
47
+ success: false,
48
+ error: err as Error,
49
+ };
50
+ }
51
+ }
@@ -0,0 +1,44 @@
1
+ import * as prompts from '@inquirer/prompts';
2
+ import { ApplianceFramework, ApplianceInput, ApplianceType } from '@appliance.sh/sdk';
3
+ import * as slug from 'random-word-slugs';
4
+
5
+ export const promptForApplianceName = (config?: Partial<ApplianceInput>) => {
6
+ const name = prompts.input({
7
+ message: `What's the name of your appliance?`,
8
+ default: config?.name ?? slug.generateSlug(2, { format: 'kebab' }),
9
+ });
10
+
11
+ return name;
12
+ };
13
+
14
+ export const promptForApplianceType = (config: Partial<ApplianceInput>) => {
15
+ const type = prompts.select<ApplianceType>({
16
+ message: `Choose a type:`,
17
+ choices: [ApplianceType.framework, ApplianceType.container],
18
+ default: config?.type ?? ApplianceType.framework,
19
+ });
20
+
21
+ return type;
22
+ };
23
+
24
+ export const promptForApplianceFramework = (config: Partial<ApplianceInput>) => {
25
+ const framework = prompts.select<ApplianceFramework>({
26
+ message: `Choose a framework:`,
27
+ choices: [ApplianceFramework.Auto, ApplianceFramework.Node, ApplianceFramework.Python],
28
+ default: (config.type === ApplianceType.framework ? config.framework : undefined) ?? ApplianceFramework.Auto,
29
+ });
30
+
31
+ return framework;
32
+ };
33
+
34
+ export const promptForAppliancePort = (config: Partial<ApplianceInput>) => {
35
+ const port = prompts.number<true>({
36
+ message: 'What port should the app listen on?',
37
+ min: 1,
38
+ max: 65535,
39
+ validate: (value) => !isNaN(parseInt(`${value}`)) || 'Please enter a valid port number',
40
+ default: (config.type === ApplianceType.container ? config.port : undefined) ?? 8080,
41
+ });
42
+
43
+ return port;
44
+ };
package/tsconfig.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "extends": "../../tsconfig.json",
3
3
  "compilerOptions": {
4
+ "module": "NodeNext",
5
+ "moduleResolution": "nodenext",
6
+ "target": "es2019",
4
7
  "outDir": "./dist",
5
8
  "declaration": true,
6
9
  "declarationDir": "./dist/types"