@swell/cli 1.0.2 → 2.0.1-alpha.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/LICENSE.md +21 -0
- package/README.md +1 -1
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +7 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +7 -0
- package/dist/app-command.d.ts +22 -0
- package/dist/app-command.js +45 -0
- package/dist/commands/app/_pull.d.ts +8 -0
- package/dist/commands/app/_pull.js +45 -0
- package/dist/commands/app/_uninstall.d.ts +12 -0
- package/dist/commands/app/_uninstall.js +45 -0
- package/dist/commands/app/info.d.ts +15 -0
- package/dist/commands/app/info.js +92 -0
- package/dist/commands/app/install.d.ts +17 -0
- package/dist/commands/app/install.js +177 -0
- package/dist/commands/app/push.d.ts +17 -0
- package/dist/commands/app/push.js +158 -0
- package/dist/commands/app/version.d.ts +18 -0
- package/dist/commands/app/version.js +156 -0
- package/dist/commands/app/watch.d.ts +15 -0
- package/dist/commands/app/watch.js +110 -0
- package/dist/commands/create/app.d.ts +21 -0
- package/dist/commands/create/app.js +210 -0
- package/dist/commands/create/content.d.ts +19 -0
- package/dist/commands/create/content.js +87 -0
- package/dist/commands/create/function.d.ts +25 -0
- package/dist/commands/create/function.js +191 -0
- package/dist/commands/create/index.d.ts +6 -0
- package/dist/commands/create/index.js +34 -0
- package/dist/commands/create/model.d.ts +19 -0
- package/dist/commands/create/model.js +87 -0
- package/dist/commands/create/notification.d.ts +25 -0
- package/dist/commands/create/notification.js +170 -0
- package/dist/commands/login.d.ts +22 -0
- package/dist/commands/login.js +77 -0
- package/dist/commands/logout.d.ts +8 -0
- package/dist/commands/logout.js +45 -0
- package/dist/commands/logs.d.ts +23 -0
- package/dist/commands/logs.js +207 -0
- package/dist/commands/switch.d.ts +10 -0
- package/dist/commands/switch.js +41 -0
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +24 -0
- package/dist/create-collection-command.d.ts +26 -0
- package/dist/create-collection-command.js +97 -0
- package/dist/create-command.d.ts +16 -0
- package/dist/create-command.js +59 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/api.d.ts +13 -0
- package/dist/lib/api.js +117 -0
- package/dist/lib/app-config.d.ts +29 -0
- package/dist/lib/app-config.js +127 -0
- package/dist/lib/apps/app-config.d.ts +42 -0
- package/dist/lib/apps/app-config.js +167 -0
- package/dist/lib/apps/index.d.ts +67 -0
- package/dist/lib/apps/index.js +167 -0
- package/dist/lib/apps/paths.d.ts +18 -0
- package/dist/lib/apps/paths.js +54 -0
- package/dist/lib/bundle.d.ts +1 -0
- package/dist/lib/bundle.js +40 -0
- package/dist/lib/config.d.ts +37 -0
- package/dist/lib/config.js +131 -0
- package/dist/lib/constants.d.ts +5 -0
- package/dist/lib/constants.js +17 -0
- package/dist/lib/create/content.d.ts +23 -0
- package/dist/lib/create/content.js +40 -0
- package/dist/lib/create/function.d.ts +17 -0
- package/dist/lib/create/function.js +36 -0
- package/dist/lib/create/index.d.ts +13 -0
- package/dist/lib/create/index.js +20 -0
- package/dist/lib/create/model.d.ts +22 -0
- package/dist/lib/create/model.js +30 -0
- package/dist/lib/create/notification.d.ts +25 -0
- package/dist/lib/create/notification.js +1 -0
- package/dist/lib/info.d.ts +38 -0
- package/dist/lib/info.js +91 -0
- package/dist/lib/logs/index.d.ts +23 -0
- package/dist/lib/logs/index.js +88 -0
- package/dist/lib/logs/line-output.d.ts +5 -0
- package/dist/lib/logs/line-output.js +13 -0
- package/dist/lib/logs/output.d.ts +7 -0
- package/dist/lib/logs/output.js +8 -0
- package/dist/lib/logs/table-output.d.ts +11 -0
- package/dist/lib/logs/table-output.js +128 -0
- package/dist/lib/sessions.d.ts +9 -0
- package/dist/lib/sessions.js +110 -0
- package/dist/lib/stores.d.ts +63 -0
- package/dist/lib/stores.js +173 -0
- package/dist/lib/style.d.ts +24 -0
- package/dist/lib/style.js +29 -0
- package/dist/lib/swell-function-wrapper.d.ts +88 -0
- package/dist/lib/swell-function-wrapper.js +302 -0
- package/dist/remote-app-command.d.ts +36 -0
- package/dist/remote-app-command.js +367 -0
- package/dist/swell-command.d.ts +21 -0
- package/dist/swell-command.js +43 -0
- package/oclif.manifest.json +1085 -0
- package/package.json +89 -42
- package/LICENSE +0 -674
- package/dist/bin/multipart-parser-85ef9626.js +0 -456
- package/dist/bin/swell-0f71f07d.js +0 -30670
- package/dist/bin/swell.js +0 -24
- package/dist/man/swell.1 +0 -85
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { confirm } from '@inquirer/prompts';
|
|
2
|
+
import { Flags } from '@oclif/core';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { appConfigFileExists } from '../../lib/app-config.js';
|
|
6
|
+
import { ConfigPaths, allConfigFilesPathsByType, appConfigFromFile, getConfigTypeFromPath, getConfigTypeKeyFromValue, } from '../../lib/apps/index.js';
|
|
7
|
+
import style from '../../lib/style.js';
|
|
8
|
+
import { RemoteAppCommand } from '../../remote-app-command.js';
|
|
9
|
+
import { default as swellConfig } from '../../lib/config.js';
|
|
10
|
+
export default class AppPush extends RemoteAppCommand {
|
|
11
|
+
static description = `Push all app configurations, a specific file, or a specific configuration
|
|
12
|
+
type to an app in your store's test environment. If the app does not exist yet,
|
|
13
|
+
it will be created and its global ID saved to a .swellrc file.
|
|
14
|
+
|
|
15
|
+
- If no file is specified, all configuration files will be pushed to the store.
|
|
16
|
+
This includes the app icon (assets/icon.png) and swell.json.
|
|
17
|
+
- If a file is specified, only that file will be pushed to the store.
|
|
18
|
+
- If a directory is specified, only files in that directory will be pushed.
|
|
19
|
+
|
|
20
|
+
App configuration directories:
|
|
21
|
+
${Object.values(ConfigPaths)
|
|
22
|
+
.map((dir) => style.path(`${dir}/`))
|
|
23
|
+
.join('\n')}`;
|
|
24
|
+
static examples = [
|
|
25
|
+
'swell app push',
|
|
26
|
+
'swell app push -f content',
|
|
27
|
+
'swell app push -f models/products.json',
|
|
28
|
+
];
|
|
29
|
+
static flags = {
|
|
30
|
+
file: Flags.string({
|
|
31
|
+
char: 'f',
|
|
32
|
+
description: 'relative path to a configuration file or directory to push',
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
static orientation = {
|
|
36
|
+
env: 'test',
|
|
37
|
+
};
|
|
38
|
+
static summary = 'Push configuration files to your Swell app.';
|
|
39
|
+
async run() {
|
|
40
|
+
const { flags } = await this.parse(AppPush);
|
|
41
|
+
const { file } = flags;
|
|
42
|
+
const currentStore = swellConfig.getDefaultStore();
|
|
43
|
+
this.api.setStoreEnv(currentStore, 'test');
|
|
44
|
+
this.app = await this.getAppWithConfig(undefined, false);
|
|
45
|
+
const confirmExistingApp = await this.confirmRemoveInstalledApp(currentStore);
|
|
46
|
+
if (confirmExistingApp === false) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// If app exists but owned by another client, offer to create a new instance
|
|
50
|
+
if (this.app.client_id !== currentStore) {
|
|
51
|
+
if (!confirmExistingApp?.id) {
|
|
52
|
+
const confirmPushNew = await confirm({
|
|
53
|
+
message: `${style.appConfigValue(this.app.name)} has a dev version in another store (${style.storeId(this.app.client_id)}). Do you want to create a new development instance in ${style.storeId(currentStore)}?`,
|
|
54
|
+
default: false,
|
|
55
|
+
});
|
|
56
|
+
if (!confirmPushNew) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// create a new app
|
|
61
|
+
this.app = await this.getCreateUpdateApp();
|
|
62
|
+
if (!this.app) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (!file) {
|
|
67
|
+
// update existing app if not targeting a file
|
|
68
|
+
this.app = await this.getCreateUpdateApp(this.app);
|
|
69
|
+
}
|
|
70
|
+
if (file) {
|
|
71
|
+
const filePath = path.join(this.appPath, file);
|
|
72
|
+
if (!appConfigFileExists(filePath)) {
|
|
73
|
+
this.error(`Path ${style.path(file)} does not exist`);
|
|
74
|
+
}
|
|
75
|
+
const filePathParsed = path.parse(file);
|
|
76
|
+
// if no directory is identified and no extension, it means the user
|
|
77
|
+
// passed in a config directory
|
|
78
|
+
if (!filePathParsed.dir && !filePathParsed.ext) {
|
|
79
|
+
await this.pushConfigPath(filePathParsed.base);
|
|
80
|
+
this.logDashboardUrl(flags, currentStore);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await this.pushFile(filePath);
|
|
84
|
+
this.logDashboardUrl(flags, currentStore);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const syncResult = await this.syncAppConfigs();
|
|
88
|
+
const { installed, removed } = syncResult;
|
|
89
|
+
this.log(`Pushed ${installed.length} ${installed.length === 1 ? 'configuration' : 'configurations'}${removed.length > 0 ? ` (removed ${removed.length}).` : '.'}`);
|
|
90
|
+
this.logDashboardUrl(flags, currentStore);
|
|
91
|
+
}
|
|
92
|
+
logDashboardUrl(flags, storeId) {
|
|
93
|
+
this.log(`View the ${style.appConfigValue(this.app.name)} app in your dashboard at ${style.link(this.dashboardUrl(flags, storeId))}.`);
|
|
94
|
+
}
|
|
95
|
+
async pushConfigPath(path) {
|
|
96
|
+
const configType = getConfigTypeFromPath(path);
|
|
97
|
+
const configTypeKey = getConfigTypeKeyFromValue(configType);
|
|
98
|
+
if (!configTypeKey) {
|
|
99
|
+
this.error(`Invalid config path: ${path}`);
|
|
100
|
+
}
|
|
101
|
+
let pushedFiles = 0;
|
|
102
|
+
for (const { configFile } of allConfigFilesPathsByType(this.appPath, configTypeKey)) {
|
|
103
|
+
const appConfig = appConfigFromFile(configFile, configType);
|
|
104
|
+
// eslint-disable-next-line no-await-in-loop
|
|
105
|
+
await this.pushConfigFile(appConfig);
|
|
106
|
+
pushedFiles += 1;
|
|
107
|
+
}
|
|
108
|
+
if (pushedFiles === 0) {
|
|
109
|
+
this.log(`No valid files to push found for ${style.path(path)}.`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.log(`Pushed ${style.path(path)} to ${style.appConfigValue(this.app.name)}.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async pushFile(file) {
|
|
116
|
+
const configType = getConfigTypeFromPath(path.basename(path.dirname(file)));
|
|
117
|
+
if (!configType) {
|
|
118
|
+
this.error(`File ${file} is not a valid file to push.`);
|
|
119
|
+
}
|
|
120
|
+
const appConfig = appConfigFromFile(file, configType);
|
|
121
|
+
await this.pushConfigFile(appConfig);
|
|
122
|
+
}
|
|
123
|
+
async confirmRemoveInstalledApp(currentStore) {
|
|
124
|
+
if (!this.app.private_id) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
const existingInstalledApp = await this.api.get({
|
|
128
|
+
adminPath: `/client/apps/${this.app.private_id}`,
|
|
129
|
+
}, { query: { versioned: true } });
|
|
130
|
+
if (existingInstalledApp) {
|
|
131
|
+
const continuePush = await confirm({
|
|
132
|
+
message: `${style.storeId(currentStore)} has an installed version of ${style.appConfigValue(this.app.name)} v${existingInstalledApp.version}. Do you want to replace it with a development instance?`,
|
|
133
|
+
default: false,
|
|
134
|
+
});
|
|
135
|
+
if (!continuePush) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
// uninstall the existing app
|
|
139
|
+
const spinner = ora();
|
|
140
|
+
spinner.start('Removing installed app...');
|
|
141
|
+
try {
|
|
142
|
+
await this.api.put({
|
|
143
|
+
adminPath: `/client/apps/${this.app.id}`,
|
|
144
|
+
}, {
|
|
145
|
+
body: { uninstalled: true },
|
|
146
|
+
});
|
|
147
|
+
spinner.succeed('Installed app removed.');
|
|
148
|
+
this.log();
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
spinner.fail(`Error removing installed app from ${style.storeId(currentStore)}.`);
|
|
152
|
+
this.logError(err.message);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return existingInstalledApp;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RemoteAppCommand } from '../../remote-app-command.js';
|
|
2
|
+
export default class AppVersion extends RemoteAppCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
nextVersion: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
'git-tag': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
|
+
message: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
static summary: string;
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
private gitCommit;
|
|
15
|
+
private gitTag;
|
|
16
|
+
private updateRemote;
|
|
17
|
+
private validateAndComputeVersion;
|
|
18
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { confirm, input } from '@inquirer/prompts';
|
|
2
|
+
import git from '@npmcli/git';
|
|
3
|
+
import { Args, Flags } from '@oclif/core';
|
|
4
|
+
import { CLIError } from '@oclif/errors';
|
|
5
|
+
import * as semver from 'semver';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import style from '../../lib/style.js';
|
|
8
|
+
import { RemoteAppCommand } from '../../remote-app-command.js';
|
|
9
|
+
const RELEASE_TYPES = [
|
|
10
|
+
'major',
|
|
11
|
+
'premajor',
|
|
12
|
+
'minor',
|
|
13
|
+
'preminor',
|
|
14
|
+
'patch',
|
|
15
|
+
'prepatch',
|
|
16
|
+
'prerelease',
|
|
17
|
+
];
|
|
18
|
+
const TAG_VERSION_PREFIX = 'v';
|
|
19
|
+
export default class AppVersion extends RemoteAppCommand {
|
|
20
|
+
static args = {
|
|
21
|
+
nextVersion: Args.string({
|
|
22
|
+
description: `semver version or release type: ${[
|
|
23
|
+
'<next.version>',
|
|
24
|
+
...RELEASE_TYPES,
|
|
25
|
+
].join('|')}`,
|
|
26
|
+
async parse(input) {
|
|
27
|
+
if (RELEASE_TYPES.includes(input)) {
|
|
28
|
+
return input;
|
|
29
|
+
}
|
|
30
|
+
const parsedInput = semver.valid(input);
|
|
31
|
+
if (parsedInput) {
|
|
32
|
+
return parsedInput;
|
|
33
|
+
}
|
|
34
|
+
throw new CLIError(`Invalid version: ${input}`);
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
static description = `When executed without specifying NEXTVERSION, the command will display the current
|
|
39
|
+
version of your app.
|
|
40
|
+
|
|
41
|
+
If a NEXTVERSION is provided, the command will validate and compute the version
|
|
42
|
+
according to Semver rules by only allowing incremental updates.
|
|
43
|
+
|
|
44
|
+
The version change will be committed to the Git repository using the provided
|
|
45
|
+
message or a default similar to the tag, e.g. "v1.0.1".
|
|
46
|
+
|
|
47
|
+
By default, it creates a Git tag for the specified version. You can disable this
|
|
48
|
+
behavior by using the --git-tag=false option.`;
|
|
49
|
+
static examples = [
|
|
50
|
+
'swell app version',
|
|
51
|
+
'swell app version minor',
|
|
52
|
+
'swell app version 1.0.0 -m "First release"',
|
|
53
|
+
];
|
|
54
|
+
static flags = {
|
|
55
|
+
'git-tag': Flags.boolean({
|
|
56
|
+
default: true,
|
|
57
|
+
description: 'create a git tag for this version (default true)',
|
|
58
|
+
}),
|
|
59
|
+
message: Flags.string({
|
|
60
|
+
char: 'm',
|
|
61
|
+
description: 'description of the changes in this version',
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
static summary = 'Increment the version of your Swell app.';
|
|
65
|
+
async run() {
|
|
66
|
+
const { args, flags } = await this.parse(AppVersion);
|
|
67
|
+
const { 'git-tag': gitTag } = flags;
|
|
68
|
+
let { message } = flags;
|
|
69
|
+
let { nextVersion } = args;
|
|
70
|
+
// load app from remote
|
|
71
|
+
this.app = await this.getAppWithConfig();
|
|
72
|
+
const remoteVersion = this.app.version;
|
|
73
|
+
const versions = await this.getVersions();
|
|
74
|
+
// if the user didn't set a version, show the current one
|
|
75
|
+
if (!nextVersion) {
|
|
76
|
+
if (versions.length > 0) {
|
|
77
|
+
const lastVersion = versions[0].version;
|
|
78
|
+
this.log(`${style.appConfigValue(this.app.name)} latest version is ${style.appConfigValue(lastVersion)}`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.log(`${style.appConfigValue(this.app.name)} has not been versioned yet`);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
nextVersion = this.validateAndComputeVersion(nextVersion, remoteVersion);
|
|
86
|
+
if (!message) {
|
|
87
|
+
message = await input({
|
|
88
|
+
message: 'Describe the changes in this version',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const continueVersion = await confirm({
|
|
92
|
+
message: `Create ${style.appConfigValue(this.app.name)} version ${nextVersion}?`,
|
|
93
|
+
});
|
|
94
|
+
if (!continueVersion) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (!message) {
|
|
98
|
+
message = `${TAG_VERSION_PREFIX}${nextVersion}`;
|
|
99
|
+
}
|
|
100
|
+
const spinner = ora();
|
|
101
|
+
spinner.start('Creating version...');
|
|
102
|
+
try {
|
|
103
|
+
await this.updateRemote(nextVersion, message, spinner);
|
|
104
|
+
await this.gitCommit(message);
|
|
105
|
+
if (gitTag)
|
|
106
|
+
await this.gitTag(nextVersion);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
spinner.fail(err.message);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
spinner.succeed(`${style.appConfigValue(this.app.name)} version ${nextVersion} created.`);
|
|
113
|
+
}
|
|
114
|
+
async gitCommit(message) {
|
|
115
|
+
// check if there are changes in the app config file
|
|
116
|
+
// if there are no changes, there is nothing to commit
|
|
117
|
+
const configStatus = await git.spawn(['status', this.appConfig.path], {
|
|
118
|
+
cwd: this.appConfig.cwd,
|
|
119
|
+
});
|
|
120
|
+
if (configStatus.stdout.includes('nothing to commit')) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// add the app config file to commit - this will contain the new version
|
|
124
|
+
await git.spawn(['add', this.appConfig.path]);
|
|
125
|
+
// commit the version change to git. It skips the git hooks with -n
|
|
126
|
+
await git.spawn(['commit', '-n', '-m', message]);
|
|
127
|
+
}
|
|
128
|
+
async gitTag(nextVersion) {
|
|
129
|
+
const tag = `${TAG_VERSION_PREFIX}${nextVersion}`;
|
|
130
|
+
// tag the version in git
|
|
131
|
+
await git.spawn(['tag', tag], { cwd: process.cwd() });
|
|
132
|
+
}
|
|
133
|
+
async updateRemote(nextVersion, message, spinner) {
|
|
134
|
+
// update the version on the remote
|
|
135
|
+
const versionResponse = await this.api.post({ adminPath: `/apps/${this.app.id}/versions` }, { body: { description: message, version: nextVersion } });
|
|
136
|
+
if (!versionResponse || !versionResponse.version) {
|
|
137
|
+
spinner.fail('Something went wrong.');
|
|
138
|
+
}
|
|
139
|
+
// update the local app config file since remote was successful
|
|
140
|
+
this.appConfig.set('version', nextVersion);
|
|
141
|
+
}
|
|
142
|
+
validateAndComputeVersion(nextVersion, remoteVersion) {
|
|
143
|
+
let version = nextVersion;
|
|
144
|
+
// compute the value of the next version and check if it's valid
|
|
145
|
+
if (RELEASE_TYPES.includes(nextVersion)) {
|
|
146
|
+
// increment the version
|
|
147
|
+
version = semver.inc(remoteVersion, version);
|
|
148
|
+
}
|
|
149
|
+
this.debug(`next version: ${version}`, `remote version: ${remoteVersion}`);
|
|
150
|
+
if (semver.eq(version, remoteVersion) ||
|
|
151
|
+
semver.lt(version, remoteVersion)) {
|
|
152
|
+
this.error(`The version you are trying to create ${version} needs to be greater than the current version ${remoteVersion}`);
|
|
153
|
+
}
|
|
154
|
+
return version;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RemoteAppCommand } from '../../remote-app-command.js';
|
|
2
|
+
export default class AppWatch extends RemoteAppCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
'sync-all': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
7
|
+
};
|
|
8
|
+
static orientation: {
|
|
9
|
+
env: string;
|
|
10
|
+
};
|
|
11
|
+
static summary: string;
|
|
12
|
+
private watchListener;
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
private handleFileChange;
|
|
15
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { appConfigFileExists } from '../../lib/app-config.js';
|
|
5
|
+
import { appConfigFromFile, findAppConfig, getConfigTypeFromPath, hasAllowedFileExtension, } from '../../lib/apps/index.js';
|
|
6
|
+
import style from '../../lib/style.js';
|
|
7
|
+
import { RemoteAppCommand } from '../../remote-app-command.js';
|
|
8
|
+
export default class AppWatch extends RemoteAppCommand {
|
|
9
|
+
static description = `Watches configuration files including functions, and automatically pushes
|
|
10
|
+
to the remote store.
|
|
11
|
+
|
|
12
|
+
Optionally synchronize all configs on change.`;
|
|
13
|
+
static examples = [
|
|
14
|
+
'swell app watch',
|
|
15
|
+
'swell app watch -a',
|
|
16
|
+
'swell app watch --sync-all',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
'sync-all': Flags.boolean({
|
|
20
|
+
char: 'a',
|
|
21
|
+
default: false,
|
|
22
|
+
description: 'synchronize all app configurations on file change',
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
static orientation = {
|
|
26
|
+
env: 'test',
|
|
27
|
+
};
|
|
28
|
+
static summary = 'Watch for changes and push app configurations.';
|
|
29
|
+
watchListener = async (eventType, filename) => {
|
|
30
|
+
const configFile = filename;
|
|
31
|
+
const pathParsed = path.parse(configFile);
|
|
32
|
+
const fileDirs = pathParsed.dir.split(path.sep);
|
|
33
|
+
// only the first directory is relevant for us
|
|
34
|
+
const configDir = fileDirs[0];
|
|
35
|
+
// we only want to watch files in the root of the app directory
|
|
36
|
+
// and with the allowed extensions
|
|
37
|
+
if (!configDir ||
|
|
38
|
+
fileDirs.length > 1 ||
|
|
39
|
+
!hasAllowedFileExtension(configFile)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const configType = getConfigTypeFromPath(configDir);
|
|
43
|
+
if (!configType) {
|
|
44
|
+
this.debug(`watchListener: ${filename} is not a valid config type`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let watchFileEvent;
|
|
48
|
+
// per node docs:
|
|
49
|
+
//
|
|
50
|
+
// On most platforms, 'rename' is emitted whenever a filename appears
|
|
51
|
+
// or disappears in the directory.
|
|
52
|
+
//
|
|
53
|
+
// we want however to identify delete file events so we can send the
|
|
54
|
+
// correct API request
|
|
55
|
+
if (eventType === 'rename') {
|
|
56
|
+
watchFileEvent = 'new';
|
|
57
|
+
if (!appConfigFileExists(path.join(this.appPath, configFile))) {
|
|
58
|
+
watchFileEvent = 'deleted';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
watchFileEvent = 'change';
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await this.handleFileChange(configFile, configType, watchFileEvent);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// we don't want to break the watcher if an error is thrown while
|
|
69
|
+
// handling a file change
|
|
70
|
+
this.debug(error);
|
|
71
|
+
// for tests though, we want to break the watcher
|
|
72
|
+
if (process.env.NODE_ENV === 'test') {
|
|
73
|
+
// throwing does not break the test so we need to hard exit and log
|
|
74
|
+
// the error
|
|
75
|
+
console.error(style.error(error));
|
|
76
|
+
// eslint-disable-next-line no-process-exit, unicorn/no-process-exit
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// refetch app to get updated configs
|
|
81
|
+
this.app = await this.getAppWithConfig(this.app.id);
|
|
82
|
+
};
|
|
83
|
+
async run() {
|
|
84
|
+
const { flags } = await this.parse(AppWatch);
|
|
85
|
+
this.app = await this.getAppWithConfig();
|
|
86
|
+
this.log('Watching for changes...');
|
|
87
|
+
if (flags['sync-all']) {
|
|
88
|
+
await this.syncAppConfigs();
|
|
89
|
+
fs.watch(this.appPath, { recursive: true }, async (eventType, filename) => {
|
|
90
|
+
if (filename && hasAllowedFileExtension(filename)) {
|
|
91
|
+
await this.syncAppConfigs();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
fs.watch(this.appPath, { recursive: true }, this.watchListener);
|
|
97
|
+
}
|
|
98
|
+
async handleFileChange(configFile, configType, event) {
|
|
99
|
+
if (event === 'deleted') {
|
|
100
|
+
const appConfig = findAppConfig(this.app, configFile, configType);
|
|
101
|
+
if (!appConfig) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
return this.removeConfig(appConfig);
|
|
105
|
+
}
|
|
106
|
+
const appConfig = appConfigFromFile(path.join(this.appPath, configFile), configType);
|
|
107
|
+
const result = await this.pushConfigFile(appConfig);
|
|
108
|
+
this.debug(`handleFileChange: ${configFile} pushed`, result);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SwellCommand } from '../../swell-command.js';
|
|
2
|
+
export default class CreateApp extends SwellCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static flags: {
|
|
7
|
+
pkg: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
static description: string;
|
|
11
|
+
static examples: {
|
|
12
|
+
command: string;
|
|
13
|
+
description: string;
|
|
14
|
+
}[];
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
private tryPackageSetup;
|
|
17
|
+
private setupPackage;
|
|
18
|
+
private findPackageManager;
|
|
19
|
+
private doesPackageManagerExist;
|
|
20
|
+
private createAppConfigFolders;
|
|
21
|
+
}
|