zcatalyst-cli 1.25.1 → 1.25.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.
@@ -23,6 +23,21 @@ const container_1 = require("./util_modules/container");
23
23
  const image_1 = require("@zcatalyst/container-plugin/out/endpoints/image");
24
24
  const fs_1 = require("./util_modules/fs");
25
25
  const util_1 = require("util");
26
+ const OCI_IMAGE_INDEX_MEDIA_TYPES = [
27
+ 'application/vnd.oci.image.index.v1+json',
28
+ 'application/vnd.docker.distribution.manifest.list.v2+json'
29
+ ];
30
+ const OCI_IMAGE_MANIFEST_MEDIA_TYPES = [
31
+ 'application/vnd.oci.image.manifest.v1+json',
32
+ 'application/vnd.docker.distribution.manifest.v2+json'
33
+ ];
34
+ function getBlobPathFromDigest(digest, sourceFile) {
35
+ const [algorithm, hash] = digest.split(':');
36
+ if (!algorithm || !hash) {
37
+ throw new error_1.default(`Corrupted image tar :: invalid digest ${digest} found in ${sourceFile}`, { exit: 1 });
38
+ }
39
+ return `blobs/${algorithm}/${hash}`;
40
+ }
26
41
  function getAppSailFilters(targetStr) {
27
42
  return targetStr.split(',').reduce((filterArr, target) => {
28
43
  const opts = target.split(':');
@@ -102,17 +117,70 @@ function validateAppSail(targDetails) {
102
117
  var _a;
103
118
  try {
104
119
  const manifestJsonStream = yield (0, image_1.getTarEntry)(imgTar, 'manifest.json');
105
- if (!manifestJsonStream) {
106
- throw new error_1.default('Corrupted image tar :: manifest.json not found in the tar', { exit: 1 });
120
+ const repositoriesStream = yield (0, image_1.getTarEntry)(imgTar, 'repositories');
121
+ const indexJsonStream = yield (0, image_1.getTarEntry)(imgTar, 'index.json');
122
+ if (!indexJsonStream) {
123
+ if (manifestJsonStream || repositoriesStream) {
124
+ throw new error_1.default('Legacy docker-archive format is not supported. Only OCI-compliant docker-archive formats (pure OCI or hybrid OCI) are supported.', { exit: 1 });
125
+ }
126
+ throw new error_1.default('Corrupted image tar :: required OCI metadata index.json not found in the tar', { exit: 1 });
127
+ }
128
+ const indexJson = yield fs_1.ASYNC.readStreamAsJSON(indexJsonStream);
129
+ if (indexJson.schemaVersion !== 2) {
130
+ throw new error_1.default('Corrupted image tar :: invalid index.json file. Unsupported schemaVersion', { exit: 1 });
131
+ }
132
+ const traversedDigests = new Set();
133
+ const resolveImageManifestPath = (manifestIndex, sourceFile) => __awaiter(this, void 0, void 0, function* () {
134
+ var _b;
135
+ const imageDescriptor = (_b = manifestIndex.manifests) === null || _b === void 0 ? void 0 : _b.find((manifest) => {
136
+ if (!(manifest === null || manifest === void 0 ? void 0 : manifest.digest)) {
137
+ return false;
138
+ }
139
+ if (!manifest.mediaType) {
140
+ return true;
141
+ }
142
+ return (OCI_IMAGE_MANIFEST_MEDIA_TYPES.includes(manifest.mediaType) ||
143
+ OCI_IMAGE_INDEX_MEDIA_TYPES.includes(manifest.mediaType));
144
+ });
145
+ if (!(imageDescriptor === null || imageDescriptor === void 0 ? void 0 : imageDescriptor.digest)) {
146
+ throw new error_1.default(`Corrupted image tar :: invalid ${sourceFile} file. Unable to identify image manifest`, { exit: 1 });
147
+ }
148
+ if (traversedDigests.has(imageDescriptor.digest)) {
149
+ throw new error_1.default(`Corrupted image tar :: circular descriptor reference found for digest ${imageDescriptor.digest}`, { exit: 1 });
150
+ }
151
+ if (!imageDescriptor.mediaType ||
152
+ OCI_IMAGE_MANIFEST_MEDIA_TYPES.includes(imageDescriptor.mediaType)) {
153
+ return getBlobPathFromDigest(imageDescriptor.digest, sourceFile);
154
+ }
155
+ traversedDigests.add(imageDescriptor.digest);
156
+ const nestedIndexPath = getBlobPathFromDigest(imageDescriptor.digest, sourceFile);
157
+ const nestedIndexStream = yield (0, image_1.getTarEntry)(imgTar, nestedIndexPath);
158
+ if (!nestedIndexStream) {
159
+ throw new error_1.default(`Corrupted image tar :: nested image index ${nestedIndexPath} not found in the tar`, { exit: 1 });
160
+ }
161
+ const nestedIndex = yield fs_1.ASYNC.readStreamAsJSON(nestedIndexStream);
162
+ if (nestedIndex.schemaVersion !== 2) {
163
+ throw new error_1.default(`Corrupted image tar :: invalid nested image index ${nestedIndexPath}. Unsupported schemaVersion`, { exit: 1 });
164
+ }
165
+ return resolveImageManifestPath(nestedIndex, nestedIndexPath);
166
+ });
167
+ const imageManifestPath = yield resolveImageManifestPath(indexJson, 'index.json');
168
+ const imageManifestStream = yield (0, image_1.getTarEntry)(imgTar, imageManifestPath);
169
+ if (!imageManifestStream) {
170
+ throw new error_1.default(`Corrupted image tar :: image manifest ${imageManifestPath} not found in the tar`, { exit: 1 });
171
+ }
172
+ const imageManifest = yield fs_1.ASYNC.readStreamAsJSON(imageManifestStream);
173
+ if (imageManifest.schemaVersion !== 2) {
174
+ throw new error_1.default('Corrupted image tar :: invalid image manifest file. Unsupported schemaVersion', { exit: 1 });
107
175
  }
108
- const manifestJson = yield fs_1.ASYNC.readStreamAsJSON(manifestJsonStream);
109
- const config = (_a = manifestJson.at(0)) === null || _a === void 0 ? void 0 : _a.Config;
110
- if (!config) {
111
- throw new error_1.default('Corrupted image tar :: invalid manifest.json file. Unable to identify the config file', { exit: 1 });
176
+ const configDigest = (_a = imageManifest.config) === null || _a === void 0 ? void 0 : _a.digest;
177
+ if (!configDigest) {
178
+ throw new error_1.default('Corrupted image tar :: invalid image manifest file. Unable to identify the config blob', { exit: 1 });
112
179
  }
113
- const configFileStream = yield (0, image_1.getTarEntry)(imgTar, config);
180
+ const configBlobPath = getBlobPathFromDigest(configDigest, imageManifestPath);
181
+ const configFileStream = yield (0, image_1.getTarEntry)(imgTar, configBlobPath);
114
182
  if (!configFileStream) {
115
- throw new error_1.default(`Corrupted image tar :: config file ${config} not found in the tar`, { exit: 1 });
183
+ throw new error_1.default(`Corrupted image tar :: config blob ${configBlobPath} not found in the tar`, { exit: 1 });
116
184
  }
117
185
  const configFile = yield fs_1.ASYNC.readStreamAsJSON(configFileStream);
118
186
  resolve(configFile);
@@ -130,7 +198,7 @@ function validateAppSail(targDetails) {
130
198
  }
131
199
  });
132
200
  const _details = targDetails.map((targ) => __awaiter(this, void 0, void 0, function* () {
133
- var _b, _c;
201
+ var _c, _d;
134
202
  if (targ.validity.valid) {
135
203
  if (targ.runtime === 'custom') {
136
204
  if (isSocket === undefined) {
@@ -172,7 +240,7 @@ function validateAppSail(targDetails) {
172
240
  }
173
241
  return targ;
174
242
  }
175
- if (((_b = targ.config) === null || _b === void 0 ? void 0 : _b.platform) !== 'war' && !((_c = targ.config) === null || _c === void 0 ? void 0 : _c.command)) {
243
+ if (((_c = targ.config) === null || _c === void 0 ? void 0 : _c.platform) !== 'war' && !((_d = targ.config) === null || _d === void 0 ? void 0 : _d.command)) {
176
244
  targ.validity = {
177
245
  valid: false,
178
246
  reason: 'Start-up command missing'
@@ -44,6 +44,7 @@ exports.default = new command_1.default('slate:link')
44
44
  .description('Links an existing local application to the Catalyst Slate service for web hosting')
45
45
  .option('--name <app_name>', 'Specify the name for your Slate application')
46
46
  .option('--framework <framework_name>', 'Specify the frontend framework used (e.g., React, Angular, Vue)')
47
+ .option('--source <path>', 'Path to the existing slate app directory')
47
48
  .option('--template <template_url>', 'Provide a template URL to initialize your Slate application')
48
49
  .option('--default', 'Use default application configurations without prompting')
49
50
  .needs('auth')
@@ -23,15 +23,18 @@ const config_1 = require("../../util_modules/config");
23
23
  const fs_1 = require("../../util_modules/fs");
24
24
  const error_1 = __importDefault(require("../../error"));
25
25
  const path_1 = require("path");
26
+ const env_1 = require("../../util_modules/env");
26
27
  exports.default = new command_1.default('slate:unlink')
27
28
  .description('Removes the linked Slate configuration from the local app')
28
29
  .option('--name <app_name>', 'Please provide name for your slate app')
30
+ .option('--remove-source', 'Delete the slate app source directory without prompting')
29
31
  .needs('auth')
30
32
  .needs('config')
31
33
  .needs('rc')
32
34
  .action(() => __awaiter(void 0, void 0, void 0, function* () {
33
35
  const config = runtime_store_1.default.get('config');
34
36
  let appName = (0, option_1.getOptionValue)('name', undefined);
37
+ const removeSourceFlag = (0, option_1.getOptionValue)('removeSource', false);
35
38
  const slateServices = config_1.slateConfig.raw();
36
39
  if ((slateServices === null || slateServices === void 0 ? void 0 : slateServices.length) === 0 || !slateServices) {
37
40
  throw new error_1.default('No slates found.', {
@@ -55,10 +58,19 @@ exports.default = new command_1.default('slate:unlink')
55
58
  arg: [(0, ansi_colors_1.bold)('catalyst slate:unlink')]
56
59
  });
57
60
  }
58
- const { removeSource } = yield prompt_1.default.ask(prompt_1.default.question('removeSource', `Do you want to delete the source?`, {
59
- type: 'confirm',
60
- default: false
61
- }));
61
+ let removeSource;
62
+ if (removeSourceFlag) {
63
+ removeSource = true;
64
+ }
65
+ else if (env_1.isCI) {
66
+ removeSource = false;
67
+ }
68
+ else {
69
+ ({ removeSource } = yield prompt_1.default.ask(prompt_1.default.question('removeSource', `Do you want to delete the source?`, {
70
+ type: 'confirm',
71
+ default: false
72
+ })));
73
+ }
62
74
  yield fs_1.ASYNC.deleteDir((0, path_1.join)(slateDetails.source, constants_1.FILENAME.cli_config));
63
75
  if (removeSource) {
64
76
  yield fs_1.ASYNC.deleteDir(slateDetails.source);
@@ -25,89 +25,72 @@ const option_1 = require("../../util_modules/option");
25
25
  const urls_1 = __importDefault(require("../../util_modules/constants/lib/urls"));
26
26
  const project_1 = require("../../util_modules/project");
27
27
  exports.default = () => __awaiter(void 0, void 0, void 0, function* () {
28
- const dId = process.env.ZC_APP_DEPLOYMENT_BUILD_ID;
29
28
  const env = getEnvironmentType();
30
- if (!dId) {
31
- const targets = yield config_1.slateConfig.getAllTargetDetails(false);
32
- if (!targets || targets.length === 0) {
33
- throw new error_1.default('No targets found');
34
- }
35
- const filtered = slate_utils_1.slateUtils.filterTargets(targets);
36
- const validTargets = filtered.filter((targ) => {
37
- var _a, _b;
38
- if ((_a = targ.validity) === null || _a === void 0 ? void 0 : _a.valid) {
39
- return targ;
40
- }
41
- (0, logger_1.labeled)('Slate: ', 'Invalid Slate service ' + (0, ansi_colors_1.bold)(targ.name || 'Unknown')).ERROR();
42
- (0, logger_1.error)('Reason: ' + ((_b = targ.validity) === null || _b === void 0 ? void 0 : _b.reason));
43
- (0, logger_1.info)();
44
- return false;
45
- });
46
- if (validTargets.length === 0) {
47
- throw new error_1.default('No valid Slate services found to deploy', {
48
- skipHelp: true
49
- });
29
+ const targets = yield config_1.slateConfig.getAllTargetDetails(false);
30
+ if (!targets || targets.length === 0) {
31
+ throw new error_1.default('No targets found');
32
+ }
33
+ const filtered = slate_utils_1.slateUtils.filterTargets(targets);
34
+ const validTargets = filtered.filter((targ) => {
35
+ var _a, _b;
36
+ if ((_a = targ.validity) === null || _a === void 0 ? void 0 : _a.valid) {
37
+ return targ;
50
38
  }
51
- const slateAppConfigs = yield (yield (0, endpoints_1.slateAPI)({ env })).getAllApps();
52
- const throbber = throbber_1.default.getInstance();
53
- const deployRes = yield validTargets.reduce((result, _targ) => __awaiter(void 0, void 0, void 0, function* () {
54
- var _a;
55
- const targ = _targ;
56
- const prevRes = yield result;
57
- try {
58
- const throbberName = `prepare_slate_${targ.name}`;
59
- throbber.add(throbberName, {
60
- text: `Preparing Slate[${targ.name}]`
61
- });
62
- const isAppExists = slateAppConfigs.find((app) => app.name === targ.name && app.app_type === 'cli');
63
- const configDetails = !isAppExists
64
- ? (_a = (yield config_1.slateConfig.getTargetDetails(targ))) === null || _a === void 0 ? void 0 : _a.config
65
- : undefined;
66
- const deploymentName = configDetails === null || configDetails === void 0 ? void 0 : configDetails.deployment_name;
67
- deploymentName ? configDetails === null || configDetails === void 0 ? true : delete configDetails.deployment_name : null;
68
- const readStream = yield slate_utils_1.slateUtils.pack(targ.source);
69
- throbber.remove(throbberName);
70
- const response = yield (yield (0, endpoints_1.slateAPI)({ env })).deploy(readStream.stream, readStream.length, targ.name, '', deploymentName ? deploymentName : '', isAppExists === null || isAppExists === void 0 ? void 0 : isAppExists.id, configDetails ? JSON.stringify(configDetails) : '', (0, option_1.getOptionValue)('m') || '');
71
- targ.details = response;
72
- }
73
- catch (err) {
74
- (0, logger_1.debug)(err);
75
- _targ.validity = {
76
- valid: false,
77
- reason: error_1.default.getErrorInstance(err).message
78
- };
79
- }
80
- prevRes.push(_targ);
81
- return Promise.resolve(prevRes);
82
- }), Promise.resolve([]));
83
- const deployedValidTargets = deployRes.filter((targ) => {
84
- var _a, _b, _c;
85
- if (!((_a = targ.validity) === null || _a === void 0 ? void 0 : _a.valid)) {
86
- (0, logger_1.warning)('Deploy of Slate [' +
87
- targ.name +
88
- '] was unsuccessful since ' +
89
- ((_b = targ.validity) === null || _b === void 0 ? void 0 : _b.reason));
90
- }
91
- return (_c = targ.validity) === null || _c === void 0 ? void 0 : _c.valid;
39
+ (0, logger_1.labeled)('Slate: ', 'Invalid Slate service ' + (0, ansi_colors_1.bold)(targ.name || 'Unknown')).ERROR();
40
+ (0, logger_1.error)('Reason: ' + ((_b = targ.validity) === null || _b === void 0 ? void 0 : _b.reason));
41
+ (0, logger_1.info)();
42
+ return false;
43
+ });
44
+ if (validTargets.length === 0) {
45
+ throw new error_1.default('No valid Slate services found to deploy', {
46
+ skipHelp: true
92
47
  });
93
- runtime_store_1.default.set('context.payload.slate.targets', deployedValidTargets);
94
- if (deployedValidTargets.length > 0) {
95
- runtime_store_1.default.set('context.payload.slate.deploy', true);
96
- }
97
48
  }
98
- else {
99
- const source = runtime_store_1.default.get('cwd');
100
- yield slate_utils_1.slateUtils.validate(source);
101
- const readStream = yield slate_utils_1.slateUtils.pack();
49
+ const slateAppConfigs = yield (yield (0, endpoints_1.slateAPI)({ env })).getAllApps();
50
+ const throbber = throbber_1.default.getInstance();
51
+ const deployRes = yield validTargets.reduce((result, _targ) => __awaiter(void 0, void 0, void 0, function* () {
52
+ var _a;
53
+ const targ = _targ;
54
+ const prevRes = yield result;
102
55
  try {
103
- const deployRes = yield (yield (0, endpoints_1.slateAPI)({ env })).deploy(readStream.stream, readStream.length, process.env.ZC_APP_NAME, dId);
104
- deployRes.name = process.env.ZC_APP_NAME;
105
- runtime_store_1.default.set('context.payload.slate.targets', [deployRes]);
106
- runtime_store_1.default.set('context.payload.slate.deploy', true);
56
+ const throbberName = `prepare_slate_${targ.name}`;
57
+ throbber.add(throbberName, {
58
+ text: `Preparing Slate[${targ.name}]`
59
+ });
60
+ const isAppExists = slateAppConfigs.find((app) => app.name === targ.name && app.app_type === 'cli');
61
+ const configDetails = !isAppExists
62
+ ? (_a = (yield config_1.slateConfig.getTargetDetails(targ))) === null || _a === void 0 ? void 0 : _a.config
63
+ : undefined;
64
+ const deploymentName = configDetails === null || configDetails === void 0 ? void 0 : configDetails.deployment_name;
65
+ deploymentName ? configDetails === null || configDetails === void 0 ? true : delete configDetails.deployment_name : null;
66
+ const readStream = yield slate_utils_1.slateUtils.pack(targ.source);
67
+ throbber.remove(throbberName);
68
+ const response = yield (yield (0, endpoints_1.slateAPI)({ env })).deploy(readStream.stream, readStream.length, targ.name, deploymentName ? deploymentName : '', isAppExists === null || isAppExists === void 0 ? void 0 : isAppExists.id, configDetails ? JSON.stringify(configDetails) : '', (0, option_1.getOptionValue)('m') || '');
69
+ targ.details = response;
107
70
  }
108
71
  catch (err) {
109
- throw err;
72
+ (0, logger_1.debug)(err);
73
+ _targ.validity = {
74
+ valid: false,
75
+ reason: error_1.default.getErrorInstance(err).message
76
+ };
110
77
  }
78
+ prevRes.push(_targ);
79
+ return Promise.resolve(prevRes);
80
+ }), Promise.resolve([]));
81
+ const deployedValidTargets = deployRes.filter((targ) => {
82
+ var _a, _b, _c;
83
+ if (!((_a = targ.validity) === null || _a === void 0 ? void 0 : _a.valid)) {
84
+ (0, logger_1.warning)('Deploy of Slate [' +
85
+ targ.name +
86
+ '] was unsuccessful since ' +
87
+ ((_b = targ.validity) === null || _b === void 0 ? void 0 : _b.reason));
88
+ }
89
+ return (_c = targ.validity) === null || _c === void 0 ? void 0 : _c.valid;
90
+ });
91
+ runtime_store_1.default.set('context.payload.slate.targets', deployedValidTargets);
92
+ if (deployedValidTargets.length > 0) {
93
+ runtime_store_1.default.set('context.payload.slate.deploy', true);
111
94
  }
112
95
  });
113
96
  function getEnvironmentType() {
@@ -11,6 +11,6 @@ declare class Slate {
11
11
  getFrameworks(): Promise<Array<FrameworkType>>;
12
12
  getAllApps(): Promise<Array<ISlateAppInfo>>;
13
13
  downloadTemplate(): Promise<unknown>;
14
- deploy(sourceStream: ReadStream, contentLength: number, appName: string, deployId: string, deploymentName?: string, appId?: string, config?: string, msg?: string): Promise<unknown>;
14
+ deploy(sourceStream: ReadStream, contentLength: number, appName: string, deploymentName?: string, appId?: string, config?: string, msg?: string): Promise<unknown>;
15
15
  }
16
16
  export default Slate;
@@ -62,9 +62,9 @@ class Slate {
62
62
  throw new error_1.default('Error while downloading the template', { exit: 2 });
63
63
  });
64
64
  }
65
- deploy(sourceStream, contentLength, appName, deployId, deploymentName, appId, config, msg) {
65
+ deploy(sourceStream, contentLength, appName, deploymentName, appId, config, msg) {
66
66
  return __awaiter(this, void 0, void 0, function* () {
67
- const formData = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ file: sourceStream }, (appId && { app_id: appId })), (deployId && { build_id: deployId })), (deploymentName && { deployment_name: deploymentName })), (appId || deployId ? {} : { name: appName })), (config && { configuration: config })), (msg && { message: msg }));
67
+ const formData = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ file: sourceStream }, (appId && { app_id: appId })), (deploymentName && { deployment_name: deploymentName })), (appId ? {} : { name: appName })), (config && { configuration: config })), (msg && { message: msg }));
68
68
  const res = yield new api_1.default(this.opts).post(`/slate/v1/project/${this.projectId}/app/upload`, {
69
69
  formData,
70
70
  json: false,
@@ -51,21 +51,71 @@ const constants_1 = require("../../../util_modules/constants");
51
51
  const project_1 = require("../../../util_modules/project");
52
52
  const toml_1 = require("../../../util_modules/toml");
53
53
  const async_1 = require("../../../util_modules/fs/lib/async");
54
+ const DEFAULT_DEV_COMMANDS = {
55
+ cra: 'PORT=$ZC_SLATE_PORT npm start',
56
+ 'create-react-app': 'PORT=$ZC_SLATE_PORT npm start',
57
+ react: 'PORT=$ZC_SLATE_PORT npm start',
58
+ 'react-vite': 'npm run dev -- --port $ZC_SLATE_PORT',
59
+ 'react-vitejs': 'npm run dev -- --port $ZC_SLATE_PORT',
60
+ nextjs: 'npm run dev -- --port $ZC_SLATE_PORT',
61
+ next: 'npm run dev -- --port $ZC_SLATE_PORT',
62
+ angular: 'npm start -- --port $ZC_SLATE_PORT',
63
+ 'vue-vite': 'npm run dev -- --port $ZC_SLATE_PORT',
64
+ vue: 'npm run dev -- --port $ZC_SLATE_PORT',
65
+ solidjs: 'npm run dev -- --port $ZC_SLATE_PORT',
66
+ 'solid-js': 'npm run dev -- --port $ZC_SLATE_PORT',
67
+ solid: 'npm run dev -- --port $ZC_SLATE_PORT',
68
+ astro: 'npm run dev -- --port $ZC_SLATE_PORT',
69
+ preact: 'npm run dev -- --port $ZC_SLATE_PORT',
70
+ svelte: 'npm run dev -- --port $ZC_SLATE_PORT',
71
+ sveltekit: 'npm run dev -- --port $ZC_SLATE_PORT',
72
+ nuxt: 'npm run dev -- --port $ZC_SLATE_PORT',
73
+ gatsby: 'npm run develop -- --port $ZC_SLATE_PORT',
74
+ remix: 'npm run dev -- --port $ZC_SLATE_PORT'
75
+ };
76
+ const GENERIC_DEV_COMMAND = 'npm run dev -- --port $ZC_SLATE_PORT';
77
+ function hasPortReference(cmd) {
78
+ if (!cmd)
79
+ return false;
80
+ return /\$\{?ZC_SLATE_PORT\}?|%ZC_SLATE_PORT%/.test(cmd);
81
+ }
82
+ function resolveDevCommand(frameworkName, frameworks) {
83
+ var _a, _b;
84
+ const fw = frameworks.find((f) => f.name === frameworkName);
85
+ const apiCmd = (_b = (_a = fw === null || fw === void 0 ? void 0 : fw.settings) === null || _a === void 0 ? void 0 : _a.dev_command) === null || _b === void 0 ? void 0 : _b.value;
86
+ if (apiCmd && hasPortReference(apiCmd)) {
87
+ return apiCmd;
88
+ }
89
+ const mapped = DEFAULT_DEV_COMMANDS[frameworkName.toLowerCase()];
90
+ if (mapped)
91
+ return mapped;
92
+ return GENERIC_DEV_COMMAND;
93
+ }
94
+ function promptFramework(frameworks, defaultName) {
95
+ return __awaiter(this, void 0, void 0, function* () {
96
+ let ordered = frameworks;
97
+ if (defaultName) {
98
+ const idx = frameworks.findIndex((f) => f.name === defaultName);
99
+ if (idx > 0) {
100
+ ordered = [frameworks[idx], ...frameworks.slice(0, idx), ...frameworks.slice(idx + 1)];
101
+ }
102
+ }
103
+ const choices = ordered.map(({ label, name }) => prompt_1.default.choice(label, { value: name, short: label }));
104
+ const defaultIdx = defaultName ? ordered.findIndex((f) => f.name === defaultName) : -1;
105
+ const { selectedFramework } = yield prompt_1.default.ask(prompt_1.default.question('selectedFramework', 'Select a framework to start with: ', Object.assign({ type: 'list', choices }, (defaultIdx >= 0 ? { default: defaultIdx } : {}))));
106
+ return selectedFramework;
107
+ });
108
+ }
54
109
  function getFrameworkOption(frameworks, source) {
55
110
  return __awaiter(this, void 0, void 0, function* () {
56
111
  let frameworkOpt = (0, option_1.getOptionValue)('framework');
57
112
  if (frameworkOpt &&
58
113
  frameworks.findIndex((framework) => framework.name === frameworkOpt) === -1) {
59
- (0, logger_1.warning)('Given framework is not supported in Slate.');
114
+ (0, logger_1.warning)(`Given framework "${frameworkOpt}" is not supported in Slate.`);
60
115
  frameworkOpt = '';
61
116
  }
62
117
  if (!frameworkOpt) {
63
- const filteredFrameworks = frameworks.filter((framework) => framework.name !== 'other');
64
- const { selectedFramework } = yield prompt_1.default.ask(prompt_1.default.question('selectedFramework', 'Select a framework to start with: ', {
65
- type: 'list',
66
- choices: filteredFrameworks.map(({ label, name }) => prompt_1.default.choice(label, { value: name, short: label }))
67
- }));
68
- frameworkOpt = selectedFramework;
118
+ frameworkOpt = yield promptFramework(frameworks.filter((f) => f.name !== 'other'));
69
119
  }
70
120
  return { name: frameworkOpt, source };
71
121
  });
@@ -140,8 +190,23 @@ function handleTemplate(appName, { templateName, frameworkName } = {}) {
140
190
  }
141
191
  function addExistingSlate(existingSlates) {
142
192
  return __awaiter(this, void 0, void 0, function* () {
143
- yield prompt_1.default.register('file-path');
144
193
  const projectRoot = (0, project_1.getProjectRoot)();
194
+ const sourceOpt = (0, option_1.getOptionValue)('source');
195
+ if (sourceOpt) {
196
+ const buildPath = (0, path_1.resolve)(projectRoot, sourceOpt);
197
+ if (existingSlates.findIndex((targ) => targ.source === buildPath) !== -1) {
198
+ throw new error_1.default(`Path ${(0, ansi_colors_1.bold)(buildPath)} is already linked to a Slate app.`, {
199
+ exit: 1
200
+ });
201
+ }
202
+ if (!(yield fs_1.ASYNC.dirExists(buildPath))) {
203
+ throw new error_1.default(`Source path ${(0, ansi_colors_1.bold)(buildPath)} does not exist.`, {
204
+ exit: 1
205
+ });
206
+ }
207
+ return buildPath;
208
+ }
209
+ yield prompt_1.default.register('file-path');
145
210
  const { sourcePath } = yield prompt_1.default.ask(prompt_1.default.question('sourcePath', 'Please provide the source path of your slate service: ', {
146
211
  type: 'file-path',
147
212
  validate: (pth) => __awaiter(this, void 0, void 0, function* () {
@@ -167,89 +232,132 @@ function addExistingSlate(existingSlates) {
167
232
  function detectFramework(source, frameworkList) {
168
233
  return __awaiter(this, void 0, void 0, function* () {
169
234
  const frameworkOpt = (0, option_1.getOptionValue)('framework');
170
- if (frameworkOpt &&
171
- frameworkList.findIndex((framework) => framework.name === frameworkOpt) === -1) {
172
- return { name: frameworkOpt, source };
235
+ if (frameworkOpt) {
236
+ if (frameworkList.findIndex((framework) => framework.name === frameworkOpt) !== -1) {
237
+ return { name: frameworkOpt, source };
238
+ }
239
+ (0, logger_1.warning)(`Given framework "${frameworkOpt}" is not supported in Slate. Auto-detecting...`);
173
240
  }
241
+ const fallback = (reason) => __awaiter(this, void 0, void 0, function* () {
242
+ (0, logger_1.warning)(reason);
243
+ const name = yield promptFramework(frameworkList.filter((f) => f.name !== 'other'));
244
+ return { name, source };
245
+ });
174
246
  const packageJsonPath = (0, path_1.join)(source, constants_1.FILENAME.package_json);
175
- const packageJsonExists = yield fs_1.ASYNC.fileExists(packageJsonPath);
176
- if (!packageJsonExists) {
177
- (0, logger_1.warning)('Unable to find the package.json file at the given source path');
178
- return getFrameworkOption(frameworkList, source);
247
+ if (!(yield fs_1.ASYNC.fileExists(packageJsonPath))) {
248
+ return fallback('Unable to find the package.json file at the given source path');
249
+ }
250
+ let data;
251
+ try {
252
+ data = yield fs_1.ASYNC.readJSONFile(packageJsonPath);
253
+ }
254
+ catch (err) {
255
+ return fallback(`Could not parse package.json (${err instanceof Error ? err.message : String(err)})`);
179
256
  }
180
- const data = yield fs_1.ASYNC.readJSONFile(packageJsonPath);
181
- const dependencies = Object.assign(Object.assign({}, data === null || data === void 0 ? void 0 : data.dependencies), data === null || data === void 0 ? void 0 : data.devDependencies);
182
- if (!dependencies) {
183
- (0, logger_1.warning)('The package.json file does not contain dependencies');
184
- return getFrameworkOption(frameworkList, source);
257
+ const dependencies = Object.assign(Object.assign({}, ((data === null || data === void 0 ? void 0 : data.dependencies) || {})), ((data === null || data === void 0 ? void 0 : data.devDependencies) || {}));
258
+ if (Object.keys(dependencies).length === 0) {
259
+ return fallback('The package.json file does not contain dependencies');
185
260
  }
186
261
  const dependencyNames = Object.keys(dependencies);
262
+ const scripts = ((data === null || data === void 0 ? void 0 : data.scripts) || {});
263
+ const scriptCommands = [scripts.dev, scripts.start, scripts.build, scripts.serve]
264
+ .filter((s) => typeof s === 'string')
265
+ .join(' ');
266
+ const scriptTokens = new Set(scriptCommands
267
+ .toLowerCase()
268
+ .replace(/[a-z_][a-z0-9_]*=\S+/gi, ' ')
269
+ .split(/[\s&|;]+/)
270
+ .filter(Boolean));
271
+ const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, '');
187
272
  const matchesKeywordPattern = (dep, keyword) => {
188
273
  if (!keyword)
189
274
  return false;
190
- const normalizedKeyword = keyword.toLowerCase().replace(/[^a-z0-9]/g, '');
191
- const normalizedDep = dep.toLowerCase().replace(/[^a-z0-9]/g, '');
192
- if (normalizedDep === normalizedKeyword) {
275
+ const k = normalize(keyword);
276
+ if (!k)
277
+ return false;
278
+ const lowerDep = dep.toLowerCase();
279
+ const normDep = normalize(dep);
280
+ if (normDep === k)
193
281
  return true;
194
- }
195
- if (normalizedDep.startsWith(`${normalizedKeyword}-`) ||
196
- normalizedDep.endsWith(`-${normalizedKeyword}`) ||
197
- normalizedDep.startsWith(`@${normalizedKeyword}/`) ||
198
- normalizedDep.endsWith(`/${normalizedKeyword}`) ||
199
- normalizedDep === `@${normalizedKeyword}`) {
282
+ if (lowerDep.startsWith(`@${keyword.toLowerCase()}/`))
200
283
  return true;
201
- }
202
- const segments = dep.split(/[-/@]/).map((s) => s.toLowerCase());
203
- if (segments.includes(normalizedKeyword)) {
284
+ if (lowerDep === `@${keyword.toLowerCase()}`)
285
+ return true;
286
+ if (normDep === `${k}js` || normDep === `${k}dom` || normDep === `${k}native`)
204
287
  return true;
205
- }
206
288
  return false;
207
289
  };
208
- let bestMatch = null;
209
- let maxMatches = 0;
290
+ const scriptKeywordHit = (keyword) => {
291
+ if (!keyword)
292
+ return false;
293
+ const k = keyword.toLowerCase();
294
+ return scriptTokens.has(k) || scriptTokens.has(normalize(keyword));
295
+ };
210
296
  const frameworkScores = [];
297
+ let bestMatch = null;
298
+ let maxScore = 0;
211
299
  for (const framework of frameworkList) {
212
300
  if (!framework.keywords || framework.keywords.length === 0 || framework.name === 'other') {
213
301
  continue;
214
302
  }
215
- const matchingKeywords = framework.keywords.filter((keyword) => {
216
- if (!keyword || keyword.trim() === '')
217
- return false;
218
- return dependencyNames.some((dep) => matchesKeywordPattern(dep, keyword));
219
- });
303
+ const matchingKeywords = framework.keywords.filter((keyword) => !!keyword &&
304
+ keyword.trim() !== '' &&
305
+ dependencyNames.some((dep) => matchesKeywordPattern(dep, keyword)));
306
+ const scriptHits = framework.keywords.filter((keyword) => scriptKeywordHit(keyword));
220
307
  const matchCount = matchingKeywords.length;
221
- let score = matchCount;
222
- const priorityKeywords = ['next', 'nuxt', 'gatsby', 'angular', 'vue', 'solid-js'];
223
- const hasPriorityKeyword = matchingKeywords.some((keyword) => priorityKeywords.includes(keyword.toLowerCase()));
224
- if (hasPriorityKeyword) {
225
- score += 10;
226
- }
308
+ let score = matchCount + scriptHits.length * 50;
227
309
  if (matchCount > 1) {
228
310
  score += matchCount * 2;
229
311
  }
230
- if (matchCount > 0) {
231
- frameworkScores.push({ framework, score, matches: matchingKeywords });
312
+ if (score > 0) {
313
+ frameworkScores.push({ framework, score, matches: matchingKeywords, scriptHits });
232
314
  }
233
- if (score > maxMatches) {
234
- maxMatches = score;
315
+ if (score > maxScore) {
316
+ maxScore = score;
235
317
  bestMatch = framework;
236
318
  }
237
319
  }
238
320
  const perfectMatches = frameworkScores.filter(({ framework }) => framework.keywords.length > 0 &&
239
321
  framework.keywords.every((keyword) => dependencyNames.some((dep) => matchesKeywordPattern(dep, keyword))));
322
+ let pick = null;
323
+ let highConfidence = false;
240
324
  if (perfectMatches.length > 0) {
241
325
  const filteredPerfectMatches = perfectMatches.filter((a) => !perfectMatches.some((b) => b !== a &&
242
326
  b.framework.keywords.length > a.framework.keywords.length &&
243
327
  a.framework.keywords.every((k) => b.framework.keywords.includes(k))));
244
- filteredPerfectMatches.sort((a, b) => b.framework.keywords.length - a.framework.keywords.length);
245
- const best = filteredPerfectMatches[0];
246
- return { name: best.framework.name, source };
328
+ filteredPerfectMatches.sort((a, b) => {
329
+ if (a.scriptHits.length !== b.scriptHits.length) {
330
+ return b.scriptHits.length - a.scriptHits.length;
331
+ }
332
+ if (a.framework.keywords.length !== b.framework.keywords.length) {
333
+ return b.framework.keywords.length - a.framework.keywords.length;
334
+ }
335
+ return b.score - a.score;
336
+ });
337
+ pick = filteredPerfectMatches[0];
338
+ highConfidence =
339
+ pick.scriptHits.length > 0 ||
340
+ (pick.framework.keywords.length > 1 && filteredPerfectMatches.length === 1);
247
341
  }
248
- if (!bestMatch) {
249
- (0, logger_1.warning)('No matching framework found in package.json dependencies');
250
- return getFrameworkOption(frameworkList, source);
342
+ else if (bestMatch) {
343
+ const best = frameworkScores.find((s) => s.framework === bestMatch);
344
+ pick = best ? { framework: best.framework, scriptHits: best.scriptHits } : null;
345
+ highConfidence = !!best && best.scriptHits.length > 0;
251
346
  }
252
- return { name: bestMatch.name, source };
347
+ if (!pick) {
348
+ return fallback('No matching framework found in package.json dependencies');
349
+ }
350
+ const pickedLabel = pick.framework.label || pick.framework.name;
351
+ if (highConfidence) {
352
+ (0, console_1.log)((0, ansi_colors_1.grey)(`Auto-detected framework: ${(0, ansi_colors_1.bold)(pickedLabel)}`));
353
+ return { name: pick.framework.name, source };
354
+ }
355
+ (0, logger_1.warning)(`Framework detection is uncertain. Best guess: ${(0, ansi_colors_1.bold)(pickedLabel)}.`);
356
+ if ((0, option_1.getOptionValue)('default')) {
357
+ return { name: pick.framework.name, source };
358
+ }
359
+ const name = yield promptFramework(frameworkList.filter((f) => f.name !== 'other'), pick.framework.name);
360
+ return { name, source };
253
361
  });
254
362
  }
255
363
  function getConfigDetails(frameworkOpt, frameworks) {
@@ -349,6 +457,9 @@ exports.default = (add) => __awaiter(void 0, void 0, void 0, function* () {
349
457
  const frameworkOpt = add
350
458
  ? yield detectFramework(yield addExistingSlate(config_1.slateConfig.raw() || []), frameworks)
351
459
  : yield getFrameworkOption(frameworks);
460
+ if (add && templateName) {
461
+ (0, logger_1.warning)('--template is ignored when linking an existing Slate app.');
462
+ }
352
463
  const appName = yield getAppName(config_1.slateConfig.raw() || [], frameworkOpt.name);
353
464
  const payload = {
354
465
  name: appName,
@@ -357,17 +468,25 @@ exports.default = (add) => __awaiter(void 0, void 0, void 0, function* () {
357
468
  };
358
469
  const slateConfigDetails = yield getConfigDetails(frameworkOpt.name, frameworks);
359
470
  if (frameworkOpt.name !== 'static') {
471
+ const defaultDevCommand = resolveDevCommand(frameworkOpt.name, frameworks);
360
472
  const slateRunConfig = (0, option_1.getOptionValue)('default')
361
- ? { devCommand: 'npm start' }
473
+ ? { devCommand: defaultDevCommand }
362
474
  : yield prompt_1.default.ask(prompt_1.default.question('devCommand', 'Please provide your Development Command:', {
363
475
  type: 'input',
364
- default: 'npm start',
476
+ default: defaultDevCommand,
365
477
  validate: (value) => __awaiter(void 0, void 0, void 0, function* () {
366
478
  value = value.trim();
367
479
  if (value === '')
368
480
  return 'Please provide a valid development command';
369
- else if (value.length > 50)
481
+ if (value.length > 50)
370
482
  return 'Cannot exceed 50 characters.';
483
+ if (!hasPortReference(value)) {
484
+ return (0, ansi_colors_1.grey)('Command must reference ' +
485
+ (0, ansi_colors_1.bold)('$ZC_SLATE_PORT') +
486
+ ' so Catalyst can bind the dev server to a free port (e.g. ' +
487
+ (0, ansi_colors_1.bold)(defaultDevCommand) +
488
+ ').');
489
+ }
371
490
  return true;
372
491
  })
373
492
  }));
@@ -8,13 +8,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  const apig_utils_1 = require("../../apig-utils");
16
+ const runtime_store_1 = __importDefault(require("../../runtime-store"));
13
17
  const config_1 = require("../../util_modules/config");
14
18
  const index_1 = require("../../util_modules/logger/index");
15
19
  exports.default = () => __awaiter(void 0, void 0, void 0, function* () {
20
+ const compiledRules = runtime_store_1.default.get('context.apig.local.config');
21
+ if (compiledRules === null)
22
+ return;
23
+ if (typeof compiledRules === 'object')
24
+ return compiledRules;
16
25
  if (!config_1.apigConfig.enabled()) {
17
- (0, index_1.info)('Skipping API Gateway rules');
26
+ runtime_store_1.default.set('context.apig.local.config', null);
27
+ (0, index_1.info)();
28
+ (0, index_1.info)('Skipping API Gateway rules as API Gateway is disabled in local');
29
+ (0, index_1.info)();
18
30
  return;
19
31
  }
20
32
  yield apig_utils_1.apigUtils.executeHook({ prefix: 'pre', command: 'serve' });
@@ -168,6 +168,9 @@ exports.default = () => __awaiter(void 0, void 0, void 0, function* () {
168
168
  serveTargets.splice(serveTargets.indexOf('client'), 1);
169
169
  }
170
170
  }
171
+ if (serveTargets.includes('apig')) {
172
+ yield features.apig();
173
+ }
171
174
  if (serveTargets.length === 0 ||
172
175
  (serveTargets.length === 1 && serveTargets[0] === 'apig')) {
173
176
  throw new index_js_1.default('No components to serve', { exit: 0, errorId: 'SERVE-1' });
@@ -88,18 +88,19 @@ function startAppSailWithContainer(serverDetails) {
88
88
  const tarStream = fs_1.SYNC.getReadStream(tarPath);
89
89
  const imgName = `${serverDetails.target.name}-tar`;
90
90
  const imageInfo = yield (0, image_1.loadTempImageTar)(imgName, tarStream);
91
- serverDetails.target.source = 'docker://' + ((_d = imageInfo.RepoTags) === null || _d === void 0 ? void 0 : _d.at(0));
91
+ const loadedImage = (_d = imageInfo.RepoTags) === null || _d === void 0 ? void 0 : _d.at(0);
92
+ serverDetails.target.source = `docker://${loadedImage}`;
92
93
  child = yield (0, container_plugin_1.start)({
93
94
  details: serverDetails,
94
95
  projectId: (0, project_1.getProjectId)(),
95
96
  ruok: container_plugin_1.RUOK.SKIP
96
97
  });
97
98
  child.once('exit', () => {
98
- (0, image_1.removeImages)(imgName).catch((er) => (0, index_1.debug)('Error removing the temp images on exit: ', er));
99
+ (0, image_1.removeImages)(loadedImage || imgName).catch((er) => (0, index_1.debug)('Error removing the temp images on exit: ', er));
99
100
  });
100
101
  child.once('error', (err) => {
101
102
  (0, index_1.debug)('Error with the AppSail container: ', err);
102
- (0, image_1.removeImages)(imgName).catch((er) => (0, index_1.debug)('Error removing the temp images on error: ', er));
103
+ (0, image_1.removeImages)(loadedImage || imgName).catch((er) => (0, index_1.debug)('Error removing the temp images on error: ', er));
103
104
  });
104
105
  break;
105
106
  }
@@ -14,7 +14,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.validateServeCommand = exports.slateUtils = void 0;
16
16
  const ansi_colors_1 = require("ansi-colors");
17
- const fs_extra_1 = require("fs-extra");
18
17
  const path_1 = require("path");
19
18
  const archiver_1 = __importDefault(require("./archiver"));
20
19
  const error_1 = __importDefault(require("./error"));
@@ -24,8 +23,6 @@ const option_1 = require("./util_modules/option");
24
23
  const constants_1 = require("./util_modules/constants");
25
24
  const logger_1 = require("./util_modules/logger");
26
25
  const minimatch_1 = __importDefault(require("minimatch"));
27
- const env_1 = require("./util_modules/env");
28
- const async_1 = require("./util_modules/fs/lib/async");
29
26
  exports.slateUtils = {
30
27
  validate: (source) => __awaiter(void 0, void 0, void 0, function* () {
31
28
  const sourceDirExists = yield fs_1.ASYNC.dirExists(source);
@@ -101,68 +98,6 @@ exports.slateUtils = {
101
98
  return refinedTargets;
102
99
  },
103
100
  pack: (source) => __awaiter(void 0, void 0, void 0, function* () {
104
- if (env_1.isCI) {
105
- source = source !== null && source !== void 0 ? source : runtime_store_1.default.get('cwd');
106
- const tempDir = (0, path_1.resolve)(constants_1.FOLDERNAME.slate);
107
- if (!(yield (0, async_1.dirExists)(tempDir))) {
108
- (0, fs_extra_1.mkdir)(tempDir);
109
- const excludePatterns = [
110
- '**/.catalyst',
111
- `**/${constants_1.FILENAME.log}`,
112
- `**/${constants_1.FILENAME.config}`,
113
- `**/${constants_1.FILENAME.rc}`,
114
- `**/${constants_1.FILENAME.app_config}`,
115
- `**/${constants_1.FILENAME.catalyst_ignore}`
116
- ];
117
- const files = yield fs_1.ASYNC.walk(source, {
118
- filter: {
119
- exclude: (path) => __awaiter(void 0, void 0, void 0, function* () {
120
- return !!excludePatterns.find((glob) => (0, minimatch_1.default)(path.replace(source + path_1.sep, ''), glob, {
121
- dot: true
122
- }));
123
- }),
124
- excludeDir: true
125
- }
126
- });
127
- const zip = new archiver_1.default('static');
128
- const fileArr = [];
129
- files.forEach((file) => {
130
- fileArr.push({
131
- path: (0, path_1.relative)(source, file.path),
132
- type: (0, path_1.extname)(file.path).replace('.', '').toUpperCase() || '',
133
- size: file.stats.size
134
- });
135
- zip.add(file.path.replace(source + path_1.sep, ''), file.stats.isSymbolicLink()
136
- ? fs_1.SYNC.readSymLink(file.path)
137
- : fs_1.SYNC.getReadStream(file.path), {
138
- mode: file.stats.mode
139
- });
140
- });
141
- const zipFinalizer = yield zip.finalize();
142
- const staticZip = (0, path_1.join)(tempDir, constants_1.FILENAME.slate.static_zip);
143
- yield zipFinalizer.writeZip(staticZip);
144
- const content = {
145
- version: '',
146
- framework: process.env.ZC_FRAMEWORK,
147
- runtime: '',
148
- static: fileArr
149
- };
150
- const configJson = (0, path_1.join)(tempDir, constants_1.FILENAME.slate.config_json);
151
- fs_1.SYNC.writeFile(configJson, JSON.stringify(content, null, 2) + '\n');
152
- }
153
- const slateZip = yield fs_1.ASYNC.walk(tempDir, { includeDirPaths: false });
154
- const zipWithJson = new archiver_1.default();
155
- slateZip.forEach((file) => {
156
- zipWithJson.add(file.path.replace(tempDir + path_1.sep, ''), file.stats.isSymbolicLink()
157
- ? fs_1.SYNC.readSymLink(file.path)
158
- : fs_1.SYNC.getReadStream(file.path), {
159
- mode: file.stats.mode
160
- });
161
- });
162
- const zipFinalizerWithJson = yield zipWithJson.finalize();
163
- yield fs_1.ASYNC.deleteDir(tempDir);
164
- return yield zipFinalizerWithJson.fsStream();
165
- }
166
101
  const excludePatterns = [
167
102
  '**/.DS_Store',
168
103
  '**/.catalyst',
@@ -172,7 +107,8 @@ exports.slateUtils = {
172
107
  `**/${constants_1.FILENAME.config}`,
173
108
  `**/${constants_1.FILENAME.rc}`,
174
109
  `**/${constants_1.FILENAME.app_config}`,
175
- `**/${constants_1.FILENAME.catalyst_ignore}`
110
+ `**/${constants_1.FILENAME.catalyst_ignore}`,
111
+ `**/${constants_1.FILENAME.cli_config}`
176
112
  ];
177
113
  const slateZip = yield fs_1.ASYNC.walk(source, {
178
114
  filter: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zcatalyst-cli",
3
- "version": "1.25.1",
3
+ "version": "1.25.3",
4
4
  "description": "Command Line Tool for CATALYST",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {