@factorialco/fcode-cli 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +334 -117
- package/dist/commands/clone.d.ts +0 -12
- package/dist/commands/clone.js +22 -135
- package/dist/commands/{settings → config}/get.d.ts +1 -1
- package/dist/commands/{settings → config}/get.js +7 -7
- package/dist/commands/{settings → config}/remove.d.ts +1 -1
- package/dist/commands/{settings → config}/remove.js +6 -6
- package/dist/commands/{settings → config}/set.d.ts +1 -1
- package/dist/commands/{settings → config}/set.js +8 -8
- package/dist/commands/dependencies/diff.d.ts +9 -0
- package/dist/commands/dependencies/diff.js +15 -0
- package/dist/commands/dependencies/pull.d.ts +9 -0
- package/dist/commands/dependencies/pull.js +44 -1
- package/dist/commands/dependencies/status.d.ts +11 -0
- package/dist/commands/dependencies/status.js +43 -0
- package/dist/commands/diff.d.ts +12 -0
- package/dist/commands/diff.js +48 -0
- package/dist/commands/http.d.ts +1 -1
- package/dist/commands/http.js +7 -7
- package/dist/commands/i18n/diff.d.ts +9 -0
- package/dist/commands/i18n/diff.js +15 -0
- package/dist/commands/i18n/status.d.ts +3 -0
- package/dist/commands/i18n/status.js +1 -0
- package/dist/commands/modules/diff.d.ts +9 -0
- package/dist/commands/modules/diff.js +15 -0
- package/dist/commands/modules/status.d.ts +3 -0
- package/dist/commands/modules/status.js +1 -0
- package/dist/commands/processes/diff.d.ts +9 -0
- package/dist/commands/processes/diff.js +15 -0
- package/dist/commands/processes/status.d.ts +3 -0
- package/dist/commands/processes/status.js +1 -0
- package/dist/commands/pull.js +3 -3
- package/dist/commands/push.js +2 -2
- package/dist/commands/{team → settings}/aliases/delete.d.ts +1 -1
- package/dist/commands/{team → settings}/aliases/delete.js +6 -6
- package/dist/commands/{team → settings}/aliases/list.d.ts +1 -1
- package/dist/commands/{team → settings}/aliases/list.js +8 -8
- package/dist/commands/{team → settings}/aliases/set.d.ts +1 -1
- package/dist/commands/{team → settings}/aliases/set.js +8 -8
- package/dist/commands/settings/diff.d.ts +8 -0
- package/dist/commands/settings/diff.js +39 -0
- package/dist/commands/{team/push.d.ts → settings/pull.d.ts} +3 -3
- package/dist/commands/settings/pull.js +41 -0
- package/dist/commands/settings/push.d.ts +13 -0
- package/dist/commands/settings/push.js +103 -0
- package/dist/commands/settings/status.d.ts +8 -0
- package/dist/commands/settings/status.js +46 -0
- package/dist/commands/{team → settings}/versions/create.d.ts +1 -2
- package/dist/commands/settings/versions/create.js +36 -0
- package/dist/commands/{team → settings}/versions/delete.d.ts +1 -1
- package/dist/commands/{team → settings}/versions/delete.js +10 -10
- package/dist/commands/{team → settings}/versions/list.d.ts +1 -1
- package/dist/commands/{team → settings}/versions/list.js +8 -8
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.js +10 -7
- package/dist/commands/team/clone.d.ts +14 -0
- package/dist/commands/team/clone.js +91 -0
- package/dist/commands/team/pull.d.ts +3 -4
- package/dist/commands/team/pull.js +25 -19
- package/dist/commands/team/status.d.ts +3 -4
- package/dist/commands/team/status.js +34 -27
- package/dist/commands/variables/diff.d.ts +9 -0
- package/dist/commands/variables/diff.js +15 -0
- package/dist/commands/variables/status.d.ts +3 -0
- package/dist/commands/variables/status.js +1 -0
- package/dist/config/dev-team.d.ts +48 -0
- package/dist/config/dev-team.js +62 -0
- package/dist/config/workspace.d.ts +2 -2
- package/dist/config/workspace.js +4 -2
- package/dist/core/commands/base-command.js +5 -0
- package/dist/core/commands/dependency-command.d.ts +2 -1
- package/dist/core/commands/dependency-command.js +3 -0
- package/dist/core/commands/i18n-command.js +2 -0
- package/dist/core/commands/modules-command.js +2 -0
- package/dist/core/commands/processes-command.js +2 -0
- package/dist/core/commands/resource-command.d.ts +26 -1
- package/dist/core/commands/resource-command.js +100 -3
- package/dist/core/commands/variables-command.d.ts +7 -0
- package/dist/core/commands/variables-command.js +13 -0
- package/dist/core/dependencies/env-dependencies-command.js +6 -3
- package/dist/core/executors/javascript-execution-task.js +36 -5
- package/dist/core/types.d.ts +59 -9
- package/dist/core/types.js +64 -10
- package/dist/core/utils/dependencies.js +4 -1
- package/dist/env.js +1 -1
- package/dist/repositories/process.d.ts +8 -2
- package/dist/repositories/process.js +29 -15
- package/dist/repositories/programming-language-manifest.d.ts +31 -1
- package/dist/repositories/programming-language-manifest.js +96 -2
- package/dist/repositories/settings.d.ts +52 -0
- package/dist/repositories/settings.js +214 -0
- package/dist/runtime/libs/python/fcode/context.py +42 -8
- package/dist/services/dashboard-client.d.ts +31 -0
- package/dist/services/dashboard-client.js +99 -0
- package/dist/services/dev-team-service.d.ts +41 -0
- package/dist/services/dev-team-service.js +162 -0
- package/dist/services/fcode-graphql-client.d.ts +17 -2
- package/dist/services/fcode-graphql-client.js +127 -15
- package/dist/services/locale-resolver.d.ts +17 -1
- package/dist/services/locale-resolver.js +62 -7
- package/dist/services/resource-differ.d.ts +60 -0
- package/dist/services/resource-differ.js +135 -0
- package/dist/services/skills-installer.d.ts +16 -0
- package/dist/services/skills-installer.js +77 -0
- package/dist/services/team-versioning.d.ts +16 -0
- package/dist/services/team-versioning.js +40 -0
- package/dist/services/webhook-auth.js +1 -1
- package/dist/services/workspace-cloner.d.ts +31 -0
- package/dist/services/workspace-cloner.js +95 -0
- package/dist/utils/dev-slug.d.ts +12 -0
- package/dist/utils/dev-slug.js +37 -0
- package/dist/utils/diff.d.ts +19 -0
- package/dist/utils/diff.js +58 -0
- package/dist/utils/gitignore.d.ts +3 -1
- package/dist/utils/gitignore.js +11 -2
- package/dist/utils/slugify.d.ts +17 -0
- package/dist/utils/slugify.js +41 -0
- package/oclif.manifest.json +433 -113
- package/package.json +12 -7
- package/dist/commands/team/push.js +0 -52
- package/dist/commands/team/versions/create.js +0 -42
- package/dist/repositories/team.d.ts +0 -31
- package/dist/repositories/team.js +0 -127
package/dist/commands/clone.d.ts
CHANGED
|
@@ -9,20 +9,8 @@ export default class Clone extends Command {
|
|
|
9
9
|
static flags: {
|
|
10
10
|
skipSkillsSetup: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
};
|
|
12
|
-
checkWorkspaceAlreadyCloned(slug: string): Promise<void>;
|
|
13
|
-
checkWorkspaceIsEmpty(slug: string): Promise<void>;
|
|
14
12
|
cloneTeam(origin: TeamOrigin, skipSkillsSetup?: boolean): Promise<void>;
|
|
15
|
-
createWorkspaceConfig(origin: TeamOrigin): Promise<void>;
|
|
16
|
-
createWorkspaceI18n(slug: string): void;
|
|
17
|
-
createWorkspaceDatastore(slug: string): void;
|
|
18
|
-
createWorkspaceStorage(slug: string): void;
|
|
19
|
-
createWorkspaceGitIgnore(slug: string): Promise<void>;
|
|
20
|
-
createWorkspaceLocalVariables(slug: string): Promise<void>;
|
|
21
|
-
private skillsInstallHint;
|
|
22
|
-
setupSkills(): Promise<void>;
|
|
23
|
-
private installSkills;
|
|
24
13
|
catch(error: any): Promise<void>;
|
|
25
14
|
init(): Promise<void>;
|
|
26
|
-
mkdirWorkspace(slug: string): Promise<void>;
|
|
27
15
|
run(): Promise<void>;
|
|
28
16
|
}
|
package/dist/commands/clone.js
CHANGED
|
@@ -1,125 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
|
-
const node_child_process_1 = require("node:child_process");
|
|
5
|
-
const fs = require("fs-extra");
|
|
6
4
|
const path = require("node:path");
|
|
7
5
|
const global_1 = require("../config/global");
|
|
8
|
-
const workspace_1 = require("../config/workspace");
|
|
9
6
|
const pull_1 = require("./pull");
|
|
10
7
|
const team_access_1 = require("../services/team-access");
|
|
8
|
+
const skills_installer_1 = require("../services/skills-installer");
|
|
9
|
+
const workspace_cloner_1 = require("../services/workspace-cloner");
|
|
11
10
|
const types_1 = require("../core/types");
|
|
12
11
|
const cli_1 = require("../utils/cli");
|
|
13
12
|
const settings_1 = require("../config/settings");
|
|
14
|
-
// Skill sources installed on clone. `release-sdk` in factorial-api-sdks is a
|
|
15
|
-
// maintainer-only skill, so we pick `factorial-api-sdks` explicitly there.
|
|
16
|
-
const SKILLS_SOURCES = [
|
|
17
|
-
{ repo: 'factorialco/factorial-code-skills', skills: '*' },
|
|
18
|
-
{ repo: 'factorialco/factorial-api-sdks', skills: 'factorial-api-sdks' },
|
|
19
|
-
];
|
|
20
13
|
class Clone extends core_1.Command {
|
|
21
|
-
async
|
|
14
|
+
async cloneTeam(origin, skipSkillsSetup = false) {
|
|
15
|
+
const workspaceDir = path.resolve(origin.slug);
|
|
22
16
|
try {
|
|
23
|
-
await
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
this.error(`Workspace '${slug}' already cloned.`, {
|
|
29
|
-
code: 'ERR_WORKSPACE_ALREADY_CLONED',
|
|
30
|
-
exit: 1,
|
|
31
|
-
suggestions: [`cd ${slug}`, 'fcode pull'],
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
async checkWorkspaceIsEmpty(slug) {
|
|
35
|
-
const files = fs.readdirSync(slug);
|
|
36
|
-
if (files.length == 0) {
|
|
37
|
-
return;
|
|
17
|
+
await (0, workspace_cloner_1.scaffoldWorkspace)(workspaceDir, origin);
|
|
38
18
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error instanceof workspace_cloner_1.WorkspaceAlreadyCloned) {
|
|
21
|
+
this.error(`Workspace '${origin.slug}' already cloned.`, {
|
|
22
|
+
code: 'ERR_WORKSPACE_ALREADY_CLONED',
|
|
23
|
+
exit: 1,
|
|
24
|
+
suggestions: [`cd ${origin.slug}`, 'fcode pull'],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (error instanceof workspace_cloner_1.WorkspaceNotEmpty) {
|
|
28
|
+
this.error(`Destination path '${origin.slug}' already exists and is not empty.`, {
|
|
29
|
+
code: 'ERR_WORKSPACE_DIR_IS_NOT_EMPTY',
|
|
30
|
+
exit: 1,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
48
34
|
}
|
|
49
|
-
|
|
50
|
-
async cloneTeam(origin, skipSkillsSetup = false) {
|
|
51
|
-
await this.mkdirWorkspace(origin.slug);
|
|
52
|
-
await this.createWorkspaceConfig(origin);
|
|
53
|
-
await this.createWorkspaceGitIgnore(origin.slug);
|
|
54
|
-
this.createWorkspaceI18n(origin.slug);
|
|
55
|
-
this.createWorkspaceDatastore(origin.slug);
|
|
56
|
-
this.createWorkspaceStorage(origin.slug);
|
|
57
|
-
await this.createWorkspaceLocalVariables(origin.slug);
|
|
58
|
-
process.chdir(origin.slug);
|
|
59
|
-
await pull_1.default.run([]);
|
|
35
|
+
await (0, workspace_cloner_1.runInWorkspace)(workspaceDir, () => pull_1.default.run([]));
|
|
60
36
|
if (!skipSkillsSetup) {
|
|
61
|
-
await
|
|
37
|
+
await (0, skills_installer_1.installSkillsAt)(workspaceDir);
|
|
62
38
|
}
|
|
63
39
|
}
|
|
64
|
-
async createWorkspaceConfig(origin) {
|
|
65
|
-
const config = new workspace_1.default(path.join(process.cwd(), origin.slug));
|
|
66
|
-
await config.init(origin);
|
|
67
|
-
}
|
|
68
|
-
// Created empty so `i18n:add` has somewhere to look before the first locale is pulled.
|
|
69
|
-
createWorkspaceI18n(slug) {
|
|
70
|
-
fs.mkdirpSync(path.join(slug, 'i18n'));
|
|
71
|
-
}
|
|
72
|
-
createWorkspaceDatastore(slug) {
|
|
73
|
-
fs.outputFileSync(path.join(slug, 'datastore.json'), '{}');
|
|
74
|
-
}
|
|
75
|
-
createWorkspaceStorage(slug) {
|
|
76
|
-
fs.mkdirSync(path.join(slug, 'storage'));
|
|
77
|
-
}
|
|
78
|
-
async createWorkspaceGitIgnore(slug) {
|
|
79
|
-
const globalConfig = new global_1.default();
|
|
80
|
-
const localVariablesFileName = await globalConfig.getSetting(settings_1.SettingKeys.LOCAL_VARIABLES_FILE_NAME);
|
|
81
|
-
const gitIgnorePatterns = [localVariablesFileName, 'datastore.json', '.env', 'storage/*'];
|
|
82
|
-
fs.outputFileSync(path.join(slug, '.gitignore'), gitIgnorePatterns.join('\n'));
|
|
83
|
-
}
|
|
84
|
-
async createWorkspaceLocalVariables(slug) {
|
|
85
|
-
const globalConfig = new global_1.default();
|
|
86
|
-
const localVariablesFileName = await globalConfig.getSetting(settings_1.SettingKeys.LOCAL_VARIABLES_FILE_NAME);
|
|
87
|
-
fs.outputFileSync(path.join(slug, localVariablesFileName), '');
|
|
88
|
-
}
|
|
89
|
-
skillsInstallHint(repo, skills) {
|
|
90
|
-
cli_1.default.log('💡 Install the Factorial Code agent skills later with:');
|
|
91
|
-
cli_1.default.log(` npx skills add ${repo} -s '${skills}' -y`);
|
|
92
|
-
}
|
|
93
|
-
async setupSkills() {
|
|
94
|
-
cli_1.default.log('');
|
|
95
|
-
// The skills CLI only installs to agents it detects; a fresh workspace has no
|
|
96
|
-
// agent directories, so create .claude/ first so Claude Code is always targeted.
|
|
97
|
-
fs.ensureDirSync('.claude');
|
|
98
|
-
for (const source of SKILLS_SOURCES) {
|
|
99
|
-
await this.installSkills(source.repo, source.skills);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
async installSkills(repo, skills) {
|
|
103
|
-
// `npx --yes skills add <repo> -s <skills> -y` — non-interactive install with
|
|
104
|
-
// the skills CLI defaults: project scope (no -g), symlinked (no --copy).
|
|
105
|
-
const skillsArgs = ['--yes', 'skills', 'add', repo, '-s', skills, '-y'];
|
|
106
|
-
await new Promise((resolve) => {
|
|
107
|
-
const child = (0, node_child_process_1.spawn)('npx', skillsArgs, { stdio: 'inherit' });
|
|
108
|
-
child.on('error', () => {
|
|
109
|
-
// npx not available (or failed to launch) — don't fail the clone, just tell the user how to do it.
|
|
110
|
-
cli_1.default.log("\n⚠️ Couldn't run npx automatically (is Node.js / npx installed?).");
|
|
111
|
-
this.skillsInstallHint(repo, skills);
|
|
112
|
-
resolve();
|
|
113
|
-
});
|
|
114
|
-
child.on('close', (code) => {
|
|
115
|
-
if (code !== 0) {
|
|
116
|
-
cli_1.default.log("\n⚠️ Skill installation didn't complete.");
|
|
117
|
-
this.skillsInstallHint(repo, skills);
|
|
118
|
-
}
|
|
119
|
-
resolve();
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
40
|
async catch(error) {
|
|
124
41
|
process.exitCode = 1;
|
|
125
42
|
const suggestions = error?.suggestions ?? [];
|
|
@@ -132,36 +49,6 @@ class Clone extends core_1.Command {
|
|
|
132
49
|
const { authUrl } = await new global_1.default().getAuthInfo(url);
|
|
133
50
|
await this.config.runHook('auth', { argv: [url, authUrl], id: 'login' });
|
|
134
51
|
}
|
|
135
|
-
async mkdirWorkspace(slug) {
|
|
136
|
-
return new Promise((resolve, reject) => {
|
|
137
|
-
fs.mkdir(slug, {}, (error) => {
|
|
138
|
-
if (error) {
|
|
139
|
-
if (error.code === 'EEXIST') {
|
|
140
|
-
this.checkWorkspaceAlreadyCloned(slug)
|
|
141
|
-
.then(() => this.checkWorkspaceIsEmpty(slug))
|
|
142
|
-
.then(() => resolve())
|
|
143
|
-
.catch((err) => reject(err));
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
reject(error);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
const globalConfig = new global_1.default();
|
|
151
|
-
globalConfig
|
|
152
|
-
.getSetting(settings_1.SettingKeys.VARIABLES_FILE_NAME)
|
|
153
|
-
.then((variablesFileName) => {
|
|
154
|
-
fs.mkdirSync(path.join(slug, 'processes'));
|
|
155
|
-
fs.mkdirSync(path.join(slug, 'modules'));
|
|
156
|
-
fs.mkdirSync(path.join(slug, 'dependencies'));
|
|
157
|
-
fs.createFileSync(path.join(slug, variablesFileName));
|
|
158
|
-
resolve();
|
|
159
|
-
})
|
|
160
|
-
.catch((err) => reject(err));
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
52
|
async run() {
|
|
166
53
|
const { args, flags } = await this.parse(Clone);
|
|
167
54
|
let { slug, url } = (0, global_1.parseTeamOrigin)(args.team || '');
|
|
@@ -5,9 +5,9 @@ const global_1 = require("../../config/global");
|
|
|
5
5
|
const settings_1 = require("../../config/settings");
|
|
6
6
|
const cli_1 = require("../../utils/cli");
|
|
7
7
|
const color_1 = require("@oclif/color");
|
|
8
|
-
class
|
|
8
|
+
class ConfigGet extends core_1.Command {
|
|
9
9
|
async run() {
|
|
10
|
-
const { args, flags } = await this.parse(
|
|
10
|
+
const { args, flags } = await this.parse(ConfigGet);
|
|
11
11
|
const { key } = args;
|
|
12
12
|
try {
|
|
13
13
|
const globalConfig = new global_1.default();
|
|
@@ -49,19 +49,19 @@ class SettingsGet extends core_1.Command {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
ConfigGet.description = 'Get a CLI setting value or all CLI settings';
|
|
53
|
+
ConfigGet.examples = [`$ fcode config:get ${settings_1.SettingKeys.DISABLE_UPDATE_NOTIFIER}`, '$ fcode config:get', '$ fcode config:get --all'];
|
|
54
|
+
ConfigGet.args = {
|
|
55
55
|
key: core_1.Args.string({
|
|
56
56
|
description: 'Setting key (optional, shows all settings if omitted)',
|
|
57
57
|
name: 'key',
|
|
58
58
|
required: false,
|
|
59
59
|
}),
|
|
60
60
|
};
|
|
61
|
-
|
|
61
|
+
ConfigGet.flags = {
|
|
62
62
|
all: core_1.Flags.boolean({
|
|
63
63
|
char: 'a',
|
|
64
64
|
description: 'Show all settings including defaults',
|
|
65
65
|
}),
|
|
66
66
|
};
|
|
67
|
-
exports.default =
|
|
67
|
+
exports.default = ConfigGet;
|
|
@@ -5,9 +5,9 @@ const global_1 = require("../../config/global");
|
|
|
5
5
|
const cli_1 = require("../../utils/cli");
|
|
6
6
|
const color_1 = require("@oclif/color");
|
|
7
7
|
const settings_1 = require("../../config/settings");
|
|
8
|
-
class
|
|
8
|
+
class ConfigRemove extends core_1.Command {
|
|
9
9
|
async run() {
|
|
10
|
-
const { args } = await this.parse(
|
|
10
|
+
const { args } = await this.parse(ConfigRemove);
|
|
11
11
|
const { key } = args;
|
|
12
12
|
try {
|
|
13
13
|
const globalConfig = new global_1.default();
|
|
@@ -23,13 +23,13 @@ class SettingsRemove extends core_1.Command {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
ConfigRemove.description = 'Remove a CLI setting value';
|
|
27
|
+
ConfigRemove.examples = [`$ fcode config:remove ${settings_1.SettingKeys.DISABLE_UPDATE_NOTIFIER}`];
|
|
28
|
+
ConfigRemove.args = {
|
|
29
29
|
key: core_1.Args.string({
|
|
30
30
|
description: 'Setting key',
|
|
31
31
|
name: 'key',
|
|
32
32
|
required: true,
|
|
33
33
|
}),
|
|
34
34
|
};
|
|
35
|
-
exports.default =
|
|
35
|
+
exports.default = ConfigRemove;
|
|
@@ -5,9 +5,9 @@ const global_1 = require("../../config/global");
|
|
|
5
5
|
const cli_1 = require("../../utils/cli");
|
|
6
6
|
const color_1 = require("@oclif/color");
|
|
7
7
|
const settings_1 = require("../../config/settings");
|
|
8
|
-
class
|
|
8
|
+
class ConfigSet extends core_1.Command {
|
|
9
9
|
async run() {
|
|
10
|
-
const { args } = await this.parse(
|
|
10
|
+
const { args } = await this.parse(ConfigSet);
|
|
11
11
|
const { key, value } = args;
|
|
12
12
|
try {
|
|
13
13
|
const globalConfig = new global_1.default();
|
|
@@ -19,12 +19,12 @@ class SettingsSet extends core_1.Command {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`$ fcode
|
|
25
|
-
`$ fcode
|
|
22
|
+
ConfigSet.description = 'Set a CLI setting value';
|
|
23
|
+
ConfigSet.examples = [
|
|
24
|
+
`$ fcode config:set ${settings_1.SettingKeys.DISABLE_UPDATE_NOTIFIER} true`,
|
|
25
|
+
`$ fcode config:set ${settings_1.SettingKeys.IGNORE_WORKSPACE_NOT_EMPTY} false`,
|
|
26
26
|
];
|
|
27
|
-
|
|
27
|
+
ConfigSet.args = {
|
|
28
28
|
key: core_1.Args.string({
|
|
29
29
|
description: 'Setting key',
|
|
30
30
|
name: 'key',
|
|
@@ -36,4 +36,4 @@ SettingsSet.args = {
|
|
|
36
36
|
required: true,
|
|
37
37
|
}),
|
|
38
38
|
};
|
|
39
|
-
exports.default =
|
|
39
|
+
exports.default = ConfigSet;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import DependencyCommand from '../../core/commands/dependency-command';
|
|
2
|
+
export default class DependenciesDiff extends DependencyCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
resourceSlug: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const types_1 = require("../../core/types");
|
|
4
|
+
const dependency_command_1 = require("../../core/commands/dependency-command");
|
|
5
|
+
class DependenciesDiff extends dependency_command_1.default {
|
|
6
|
+
async run() {
|
|
7
|
+
if (await this.resourcesDiff()) {
|
|
8
|
+
this.exit(1);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
DependenciesDiff.description = 'Show a unified diff between local dependency manifests and the cloud. Exits 1 when they differ';
|
|
13
|
+
DependenciesDiff.examples = ['$ fcode dependencies:diff', '$ fcode dependencies:diff JAVASCRIPT'];
|
|
14
|
+
DependenciesDiff.args = types_1.ResourceSlugArg;
|
|
15
|
+
exports.default = DependenciesDiff;
|
|
@@ -8,5 +8,14 @@ export default class DependenciesPull extends DependencyCommand {
|
|
|
8
8
|
static flags: {
|
|
9
9
|
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Writes the published snapshots into `dependencies/versions/<tag>/`, and drops the folders of
|
|
13
|
+
* tags deleted in the cloud — the same reconciliation `processes:pull` does. Unlike processes,
|
|
14
|
+
* there is no push counterpart: these files are a read-only record of what each release installs.
|
|
15
|
+
*
|
|
16
|
+
* A tag folder holds one file per language, so it is only removed when no language still publishes
|
|
17
|
+
* it — which is why pulling a single language leaves the other's files (and the folder) alone.
|
|
18
|
+
*/
|
|
19
|
+
pullVersioned(): Promise<void>;
|
|
11
20
|
run(): Promise<void>;
|
|
12
21
|
}
|
|
@@ -1,10 +1,53 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
3
|
+
const color_1 = require("@oclif/color");
|
|
4
4
|
const dependency_command_1 = require("../../core/commands/dependency-command");
|
|
5
|
+
const types_1 = require("../../core/types");
|
|
6
|
+
const cli_1 = require("../../utils/cli");
|
|
5
7
|
class DependenciesPull extends dependency_command_1.default {
|
|
8
|
+
/**
|
|
9
|
+
* Writes the published snapshots into `dependencies/versions/<tag>/`, and drops the folders of
|
|
10
|
+
* tags deleted in the cloud — the same reconciliation `processes:pull` does. Unlike processes,
|
|
11
|
+
* there is no push counterpart: these files are a read-only record of what each release installs.
|
|
12
|
+
*
|
|
13
|
+
* A tag folder holds one file per language, so it is only removed when no language still publishes
|
|
14
|
+
* it — which is why pulling a single language leaves the other's files (and the folder) alone.
|
|
15
|
+
*/
|
|
16
|
+
async pullVersioned() {
|
|
17
|
+
const repository = this._resourceRepository;
|
|
18
|
+
const languages = types_1.PROGRAMMING_LANGUAGES.filter((language) => this.filterResource(language));
|
|
19
|
+
const remoteTags = new Set();
|
|
20
|
+
for (const programmingLanguage of languages) {
|
|
21
|
+
const versions = await this._versionedResourceQuery(programmingLanguage);
|
|
22
|
+
if (versions === null) {
|
|
23
|
+
// The api predates dependency versioning: nothing to write, and no cloud state to
|
|
24
|
+
// reconcile the local folders against — deleting them would misread "cannot say" as
|
|
25
|
+
// "none published".
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const version of versions) {
|
|
29
|
+
remoteTags.add(version.tag);
|
|
30
|
+
if (!repository.versionExistsLocally(version)) {
|
|
31
|
+
cli_1.default.log(color_1.default.cyan(`🗂️ pulling ${programmingLanguage} dependencies version '${version.tag}'...`));
|
|
32
|
+
}
|
|
33
|
+
await repository.saveVersion(version);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Only reconcile deletions on a full pull: with a single language asked for, a tag missing
|
|
37
|
+
// here may still be published by the other one.
|
|
38
|
+
if (languages.length < types_1.PROGRAMMING_LANGUAGES.length) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const localTag of repository.getLocalVersionTags()) {
|
|
42
|
+
if (!remoteTags.has(localTag)) {
|
|
43
|
+
cli_1.default.log(color_1.default.cyan(`🗑️ removing dependencies version '${localTag}'...`));
|
|
44
|
+
repository.removeVersion(localTag);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
6
48
|
async run() {
|
|
7
49
|
await super.pullResources();
|
|
50
|
+
await this.pullVersioned();
|
|
8
51
|
}
|
|
9
52
|
}
|
|
10
53
|
DependenciesPull.description = 'Fetch all dependencies from cloud and save them locally';
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
import { table } from '../../utils/cli/styled/table';
|
|
1
2
|
import DependencyCommand from '../../core/commands/dependency-command';
|
|
3
|
+
import { type MergedResource } from '../../core/types';
|
|
4
|
+
export declare const inheritedCount: (resource: MergedResource) => number;
|
|
5
|
+
/**
|
|
6
|
+
* A manifest is never itself inherited — every workspace owns one per language — so the column
|
|
7
|
+
* counts the entries its parents contribute rather than naming one workspace the way the other
|
|
8
|
+
* `status` commands do. Empty when nothing is inherited, so the column is dropped entirely, like
|
|
9
|
+
* theirs.
|
|
10
|
+
*/
|
|
11
|
+
export declare const inheritedColumn: (resources: Array<MergedResource>) => table.Columns<MergedResource>;
|
|
2
12
|
export default class DependenciesStatus extends DependencyCommand {
|
|
3
13
|
static description: string;
|
|
4
14
|
static examples: string[];
|
|
5
15
|
static args: {
|
|
6
16
|
resourceSlug: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
17
|
};
|
|
18
|
+
resourcesTableLog(resources: Array<MergedResource>): Promise<void>;
|
|
8
19
|
run(): Promise<void>;
|
|
9
20
|
}
|
|
@@ -1,8 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inheritedColumn = exports.inheritedCount = void 0;
|
|
4
|
+
const cli_1 = require("../../utils/cli");
|
|
3
5
|
const dependency_command_1 = require("../../core/commands/dependency-command");
|
|
4
6
|
const types_1 = require("../../core/types");
|
|
7
|
+
const colorize_resource_1 = require("../../utils/colorize-resource");
|
|
8
|
+
const inheritedCount = (resource) => resource.inheritedDependencies?.length ?? 0;
|
|
9
|
+
exports.inheritedCount = inheritedCount;
|
|
10
|
+
/**
|
|
11
|
+
* A manifest is never itself inherited — every workspace owns one per language — so the column
|
|
12
|
+
* counts the entries its parents contribute rather than naming one workspace the way the other
|
|
13
|
+
* `status` commands do. Empty when nothing is inherited, so the column is dropped entirely, like
|
|
14
|
+
* theirs.
|
|
15
|
+
*/
|
|
16
|
+
const inheritedColumn = (resources) => {
|
|
17
|
+
if (!resources.some((resource) => (0, exports.inheritedCount)(resource) > 0)) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
inherited: {
|
|
22
|
+
get(resource) {
|
|
23
|
+
const count = (0, exports.inheritedCount)(resource);
|
|
24
|
+
return (0, colorize_resource_1.colorizeByStatus)(count > 0 ? `🔗 ${count}` : '', resource.status);
|
|
25
|
+
},
|
|
26
|
+
header: 'inherited',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
exports.inheritedColumn = inheritedColumn;
|
|
5
31
|
class DependenciesStatus extends dependency_command_1.default {
|
|
32
|
+
async resourcesTableLog(resources) {
|
|
33
|
+
cli_1.cli.table(resources, {
|
|
34
|
+
name: {
|
|
35
|
+
get({ slug, status }) {
|
|
36
|
+
return (0, colorize_resource_1.colorizeByStatus)(slug, status);
|
|
37
|
+
},
|
|
38
|
+
header: 'name',
|
|
39
|
+
},
|
|
40
|
+
...(0, exports.inheritedColumn)(resources),
|
|
41
|
+
status: {
|
|
42
|
+
get({ status }) {
|
|
43
|
+
return (0, colorize_resource_1.colorizeByStatus)(status, status);
|
|
44
|
+
},
|
|
45
|
+
header: 'status',
|
|
46
|
+
},
|
|
47
|
+
}, { sort: 'name' });
|
|
48
|
+
}
|
|
6
49
|
async run() {
|
|
7
50
|
await this.resourcesLog();
|
|
8
51
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import BaseCommand from '../core/commands/base-command';
|
|
2
|
+
/**
|
|
3
|
+
* Runs one resource's diff and reports whether it found changes, instead of letting its
|
|
4
|
+
* `this.exit(1)` end the process: every resource must be diffed before the aggregate can
|
|
5
|
+
* exit with a verdict for all of them.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runCollectingExit(run: () => Promise<unknown>): Promise<boolean>;
|
|
8
|
+
export default class Diff extends BaseCommand {
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runCollectingExit = runCollectingExit;
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const base_command_1 = require("../core/commands/base-command");
|
|
6
|
+
const diff_1 = require("./modules/diff");
|
|
7
|
+
const diff_2 = require("./processes/diff");
|
|
8
|
+
const diff_3 = require("./variables/diff");
|
|
9
|
+
const diff_4 = require("./i18n/diff");
|
|
10
|
+
const diff_5 = require("./dependencies/diff");
|
|
11
|
+
const diff_6 = require("./settings/diff");
|
|
12
|
+
/**
|
|
13
|
+
* Runs one resource's diff and reports whether it found changes, instead of letting its
|
|
14
|
+
* `this.exit(1)` end the process: every resource must be diffed before the aggregate can
|
|
15
|
+
* exit with a verdict for all of them.
|
|
16
|
+
*/
|
|
17
|
+
async function runCollectingExit(run) {
|
|
18
|
+
try {
|
|
19
|
+
await run();
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error instanceof core_1.Errors.ExitError && error.oclif.exit === 1) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class Diff extends base_command_1.default {
|
|
30
|
+
async run() {
|
|
31
|
+
await this.parse(Diff);
|
|
32
|
+
await this.logActiveTeam();
|
|
33
|
+
const results = [
|
|
34
|
+
await runCollectingExit(() => diff_2.default.run([])),
|
|
35
|
+
await runCollectingExit(() => diff_1.default.run([])),
|
|
36
|
+
await runCollectingExit(() => diff_3.default.run([])),
|
|
37
|
+
await runCollectingExit(() => diff_4.default.run([])),
|
|
38
|
+
await runCollectingExit(() => diff_5.default.run([])),
|
|
39
|
+
await runCollectingExit(() => diff_6.default.run([])),
|
|
40
|
+
];
|
|
41
|
+
if (results.some(Boolean)) {
|
|
42
|
+
this.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
Diff.description = 'Show a unified diff between the local workspace and the cloud. Exits 1 when they differ';
|
|
47
|
+
Diff.examples = ['$ fcode diff'];
|
|
48
|
+
exports.default = Diff;
|
package/dist/commands/http.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export default class Http extends Command {
|
|
|
14
14
|
* Enforces the webhook's configured auth, simulating the cloud webhook endpoint: the effective
|
|
15
15
|
* configuration names a variable, whose value is looked up in the workspace variables and compared
|
|
16
16
|
* against the configured header. A missing or malformed header, an undefined variable, a mismatch,
|
|
17
|
-
* or a webhook inheriting a workspace configuration that
|
|
17
|
+
* or a webhook inheriting a workspace configuration that settings.json does not have, is rejected.
|
|
18
18
|
* Returns true when the request may proceed, false after writing a 403.
|
|
19
19
|
*/
|
|
20
20
|
private _checkWebhookAuth;
|
package/dist/commands/http.js
CHANGED
|
@@ -7,7 +7,7 @@ const process_runner_1 = require("../services/process-runner");
|
|
|
7
7
|
const process_1 = require("../repositories/process");
|
|
8
8
|
const variable_1 = require("../repositories/variable");
|
|
9
9
|
const workspace_1 = require("../config/workspace");
|
|
10
|
-
const
|
|
10
|
+
const settings_1 = require("../repositories/settings");
|
|
11
11
|
const webhook_auth_1 = require("../services/webhook-auth");
|
|
12
12
|
const types_1 = require("../core/types");
|
|
13
13
|
class Http extends core_1.Command {
|
|
@@ -19,7 +19,7 @@ class Http extends core_1.Command {
|
|
|
19
19
|
: null;
|
|
20
20
|
const { slug: teamSlug } = await new workspace_1.default().getTeamOrigin();
|
|
21
21
|
const processRepository = new process_1.default();
|
|
22
|
-
const
|
|
22
|
+
const settingsRepository = new settings_1.default();
|
|
23
23
|
if (useBasicAuth) {
|
|
24
24
|
this.warn(`--auth-user/--auth-password protect the whole server through the ${types_1.DEFAULT_WEBHOOK_AUTH_HEADER} header, ` +
|
|
25
25
|
`so a webhook expecting its credential there cannot be exercised at the same time.`);
|
|
@@ -86,7 +86,7 @@ class Http extends core_1.Command {
|
|
|
86
86
|
res.end(JSON.stringify({ success: false, error: `Process slug required in path (e.g. /api/${teamSlug}/webhooks/my-process)` }));
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
|
-
if (!(await this._checkWebhookAuth(req, res, processRepository,
|
|
89
|
+
if (!(await this._checkWebhookAuth(req, res, processRepository, settingsRepository, slug))) {
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
await this._handleProcessExecution(req, res, slug, urlObj, logLevel, jsonLogs);
|
|
@@ -122,10 +122,10 @@ class Http extends core_1.Command {
|
|
|
122
122
|
* Enforces the webhook's configured auth, simulating the cloud webhook endpoint: the effective
|
|
123
123
|
* configuration names a variable, whose value is looked up in the workspace variables and compared
|
|
124
124
|
* against the configured header. A missing or malformed header, an undefined variable, a mismatch,
|
|
125
|
-
* or a webhook inheriting a workspace configuration that
|
|
125
|
+
* or a webhook inheriting a workspace configuration that settings.json does not have, is rejected.
|
|
126
126
|
* Returns true when the request may proceed, false after writing a 403.
|
|
127
127
|
*/
|
|
128
|
-
async _checkWebhookAuth(req, res, processRepository,
|
|
128
|
+
async _checkWebhookAuth(req, res, processRepository, settingsRepository, slug) {
|
|
129
129
|
let webhook;
|
|
130
130
|
try {
|
|
131
131
|
webhook = (await processRepository.find(slug)).webhook;
|
|
@@ -134,9 +134,9 @@ class Http extends core_1.Command {
|
|
|
134
134
|
// Unknown process: let the execution path report the 404.
|
|
135
135
|
return true;
|
|
136
136
|
}
|
|
137
|
-
// Read per request, like the process metadata above, so editing
|
|
137
|
+
// Read per request, like the process metadata above, so editing settings.json while the server
|
|
138
138
|
// runs takes effect without a restart.
|
|
139
|
-
const teamWebhookAuth = (await
|
|
139
|
+
const teamWebhookAuth = (await settingsRepository.find()).webhookAuth;
|
|
140
140
|
let effectiveAuth;
|
|
141
141
|
try {
|
|
142
142
|
effectiveAuth = (0, webhook_auth_1.resolveEffectiveAuth)(webhook, teamWebhookAuth);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import I18nCommand from '../../core/commands/i18n-command';
|
|
2
|
+
export default class I18nDiff extends I18nCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
resourceSlug: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|