@factorialco/fcode-cli 1.1.0 → 1.2.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 CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  Factorial Code is the the all-in-one platform that connects your services and APIs in the most agile way. You may discover every single piece of the Factorial Code Universe in the following links:
4
4
 
5
- - [Factorial Developer Portal](https://code.factorial.dev)
6
- - [Factorial Code Docs](https://code.factorial.dev/docs)
5
+ - [Factorial Developer Portal](https://code.factorialhr.com)
6
+ - [Factorial Code Docs](https://code.factorialhr.com/docs)
7
7
 
8
- Factorial Code provides both a Graphic User Interface (GUI), accessible at https://code.factorial.dev, and a CLI for interacting with Factorial Code Cloud.
8
+ Factorial Code provides both a Graphic User Interface (GUI), accessible at https://code.factorialhr.com, and a CLI for interacting with Factorial Code Cloud.
9
9
 
10
10
  The Factorial Code Command Line Interface facilitates interaction with Factorial Code Cloud directly from your local workstation's command line. It's particularly useful if you prefer developing and testing processes' source code locally rather than using the web IDE of Factorial Code Cloud.
11
11
 
12
- See full documentation at [https://code.factorial.dev/docs/cli/](https://code.factorial.dev/docs/cli/).
12
+ See full documentation at [https://code.factorialhr.com/docs/cli/](https://code.factorialhr.com/docs/cli/).
13
13
 
14
14
  For a guide on writing and running tests for your processes, see [TEST.md](TEST.md).
15
15
 
@@ -20,7 +20,7 @@ $ npm install -g @factorialco/fcode-cli
20
20
  $ fcode COMMAND
21
21
  running command...
22
22
  $ fcode (--version)
23
- @factorialco/fcode-cli/1.1.0 linux-x64 node-v22.22.3
23
+ @factorialco/fcode-cli/1.2.0 linux-x64 node-v22.22.3
24
24
  $ fcode --help [COMMAND]
25
25
  USAGE
26
26
  $ fcode COMMAND
@@ -295,14 +295,15 @@ USAGE
295
295
 
296
296
  FLAGS
297
297
  -a, --authUrl=<value> Factorial Code Auth endpoint (including realm, ie:
298
- https://code.factorial.dev/auth/realms/fcode)). Only needed if auth domain is different
298
+ https://code.factorialhr.com/auth/realms/fcode)). Only needed if auth domain is different
299
299
  than used cloud endpoint
300
300
  -c, --clientId=<value> Client ID (for oauth, to be used with Client Secret)
301
301
  -e, --email=<value> Email (to be used with password)
302
302
  -p, --password=<value> Password (to be used with email)
303
303
  -s, --clientSecret=<value> Client Secret (for oauth, to be used with Client ID)
304
304
  -t, --accessToken=<value> Access Token (to be used instead of email and password)
305
- -u, --url=<value> [default: https://code.factorial.dev/platform] Factorial Code Cloud endpoint
305
+ -u, --url=<value> [default: https://code.factorialhr.com/platform] Factorial Code Cloud endpoint (host or
306
+ full /platform URL, ie: code.factorialhr.com)
306
307
  -x, --prompt Prompt for email and password (instead of opening browser)
307
308
 
308
309
  DESCRIPTION
@@ -325,7 +326,8 @@ USAGE
325
326
  $ fcode logout [-u <value>]
326
327
 
327
328
  FLAGS
328
- -u, --url=<value> [default: https://code.factorial.dev/platform] Factorial Code Cloud endpoint
329
+ -u, --url=<value> [default: https://code.factorialhr.com/platform] Factorial Code Cloud endpoint (host or full
330
+ /platform URL, ie: code.factorialhr.com)
329
331
 
330
332
  DESCRIPTION
331
333
  Logout from Factorial Code
@@ -2,12 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const global_1 = require("../config/global");
5
+ const url_utils_1 = require("../utils/url-utils");
5
6
  const cli_1 = require("../utils/cli");
6
7
  class Login extends core_1.Command {
7
8
  async init() {
8
9
  const { flags } = await this.parse(Login);
10
+ const url = (0, url_utils_1.buildCloudUrl)(flags.url);
9
11
  const { failures } = await this.config.runHook('auth', {
10
- argv: [flags.url, flags.authUrl, flags.email, flags.password, flags.clientId, flags.clientSecret, flags.accessToken, flags.prompt],
12
+ argv: [url, flags.authUrl, flags.email, flags.password, flags.clientId, flags.clientSecret, flags.accessToken, flags.prompt],
11
13
  id: 'login',
12
14
  });
13
15
  if (failures.length > 0) {
@@ -16,7 +18,7 @@ class Login extends core_1.Command {
16
18
  }
17
19
  async run() {
18
20
  const { flags } = await this.parse(Login);
19
- const userInfo = await new global_1.default().getUserInfo(flags.url);
21
+ const userInfo = await new global_1.default().getUserInfo((0, url_utils_1.buildCloudUrl)(flags.url));
20
22
  cli_1.default.log(`👋 Hi, ${userInfo.name}!`);
21
23
  }
22
24
  }
@@ -26,12 +28,12 @@ Login.examples = ['$ fcode login', '$ fcode login --prompt', '$ fcode login -e y
26
28
  Login.flags = {
27
29
  url: core_1.Flags.string({
28
30
  char: 'u',
29
- description: 'Factorial Code Cloud endpoint',
31
+ description: 'Factorial Code Cloud endpoint (host or full /platform URL, ie: code.factorialhr.com)',
30
32
  default: global_1.FCODE_CLOUD_URL,
31
33
  }),
32
34
  authUrl: core_1.Flags.string({
33
35
  char: 'a',
34
- description: 'Factorial Code Auth endpoint (including realm, ie: https://code.factorial.dev/auth/realms/fcode)). Only needed if auth domain is different than used cloud endpoint',
36
+ description: 'Factorial Code Auth endpoint (including realm, ie: https://code.factorialhr.com/auth/realms/fcode)). Only needed if auth domain is different than used cloud endpoint',
35
37
  }),
36
38
  email: core_1.Flags.string({
37
39
  char: 'e',
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const auth_service_1 = require("../services/auth-service");
5
5
  const global_1 = require("../config/global");
6
+ const url_utils_1 = require("../utils/url-utils");
6
7
  const errors_1 = require("../core/errors");
7
8
  const cli_1 = require("../utils/cli");
8
9
  class Logout extends core_1.Command {
9
10
  async run() {
10
11
  const { flags } = await this.parse(Logout);
11
12
  try {
12
- await auth_service_1.default.logout(flags.url);
13
+ await auth_service_1.default.logout((0, url_utils_1.buildCloudUrl)(flags.url));
13
14
  }
14
15
  catch (error) {
15
16
  if (!(error instanceof errors_1.NotAuthenticated)) {
@@ -27,7 +28,7 @@ Logout.examples = ['$ fcode logout'];
27
28
  Logout.flags = {
28
29
  url: core_1.Flags.string({
29
30
  char: 'u',
30
- description: 'Factorial Code Cloud endpoint',
31
+ description: 'Factorial Code Cloud endpoint (host or full /platform URL, ie: code.factorialhr.com)',
31
32
  default: global_1.FCODE_CLOUD_URL,
32
33
  }),
33
34
  };
@@ -7,21 +7,24 @@ const path = require("node:path");
7
7
  const errors_1 = require("../core/errors");
8
8
  const index_1 = require("./index");
9
9
  const settings_1 = require("./settings");
10
- exports.FCODE_CLOUD_URL = process.env.FCODE_CLOUD_URL || 'https://code.factorial.dev/platform';
10
+ const url_utils_1 = require("../utils/url-utils");
11
+ exports.FCODE_CLOUD_URL = process.env.FCODE_CLOUD_URL || 'https://code.factorialhr.com/platform';
11
12
  function parseTeamOrigin(slug) {
12
13
  if (!slug) {
13
14
  return { url: exports.FCODE_CLOUD_URL, slug: '' };
14
15
  }
15
- let url;
16
16
  try {
17
- const { origin } = new URL(slug);
18
- slug = slug.replaceAll(origin + '/', '');
19
- url = origin;
17
+ const parsed = new URL(slug);
18
+ const url = (0, url_utils_1.buildCloudUrl)(parsed.origin);
19
+ const segments = parsed.pathname.split('/').filter(Boolean);
20
+ if (segments[0] === 'platform') {
21
+ segments.shift();
22
+ }
23
+ return { url, slug: segments.join('/') };
20
24
  }
21
25
  catch (e) {
22
- url = exports.FCODE_CLOUD_URL;
26
+ return { url: exports.FCODE_CLOUD_URL, slug };
23
27
  }
24
- return { url, slug };
25
28
  }
26
29
  class GlobalConfig extends index_1.default {
27
30
  constructor() {
@@ -587,7 +587,14 @@ const main = typeof _hooksModule['${fn}'] === 'function'
587
587
  console.log('[fcode] variables.delete removed ' + key + ' from variables.local.env (local run).');
588
588
  }
589
589
  };
590
- })()
590
+ })(),
591
+ sendMail: async ({ to, subject, text, html } = {}) => {
592
+ // No manager locally: log the email contents instead of sending, so
593
+ // devs can see exactly what would be delivered when run in the cloud.
594
+ console.log('[fcode] sendMail (local run) — not sent, logging contents below:');
595
+ console.log(JSON.stringify({ to, subject, text, html }, null, 2));
596
+ return { messageId: 'local-run', accepted: [].concat(to || []), rejected: [] };
597
+ }
591
598
  };
592
599
  ${Object.entries(this.getVariablesObject())
593
600
  .map(([key, value]) => `process.env['${key}'] = process.env['${key}'] ?? ${JSON.stringify(value)}`)
package/dist/env.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Validated environment schema for the fcode CLI. This is the single source of truth
4
+ * for the environment variables the CLI reads — add new vars here.
5
+ *
6
+ * Unlike the long-running services (storage-api, ai), a CLI reads config lazily and
7
+ * different commands need different vars, so there is currently NO universally required
8
+ * variable: every entry is optional or carries a default that preserves prior behavior.
9
+ * The fail-fast (❌ + exit 1) path is kept for parity with the services and so that a
10
+ * genuinely-required var added later fails at process start listing every problem.
11
+ */
12
+ export declare const envSchema: z.ZodObject<{
13
+ FCODE_CLOUD_URL: z.ZodDefault<z.ZodString>;
14
+ FCODE_AUTH_URL: z.ZodOptional<z.ZodString>;
15
+ FCODE_CLIENT_ID: z.ZodDefault<z.ZodString>;
16
+ FCODE_CONFIG_PATH: z.ZodOptional<z.ZodString>;
17
+ IGNORE_WORKSPACE_NOT_EMPTY: z.ZodOptional<z.ZodEnum<{
18
+ true: "true";
19
+ false: "false";
20
+ }>>;
21
+ FCODE_DISABLE_UPDATE_NOTIFIER: z.ZodOptional<z.ZodEnum<{
22
+ true: "true";
23
+ false: "false";
24
+ }>>;
25
+ SILENT_MODE: z.ZodOptional<z.ZodEnum<{
26
+ true: "true";
27
+ false: "false";
28
+ }>>;
29
+ CLI_UX_SKIP_TTY_CHECK: z.ZodOptional<z.ZodString>;
30
+ YC_COMMAND: z.ZodOptional<z.ZodString>;
31
+ }, z.core.$strip>;
32
+ export declare const env: {
33
+ FCODE_CLOUD_URL: string;
34
+ FCODE_CLIENT_ID: string;
35
+ FCODE_AUTH_URL?: string | undefined;
36
+ FCODE_CONFIG_PATH?: string | undefined;
37
+ IGNORE_WORKSPACE_NOT_EMPTY?: "true" | "false" | undefined;
38
+ FCODE_DISABLE_UPDATE_NOTIFIER?: "true" | "false" | undefined;
39
+ SILENT_MODE?: "true" | "false" | undefined;
40
+ CLI_UX_SKIP_TTY_CHECK?: string | undefined;
41
+ YC_COMMAND?: string | undefined;
42
+ };
43
+ export default env;
package/dist/env.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.env = exports.envSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ /**
6
+ * Validated environment schema for the fcode CLI. This is the single source of truth
7
+ * for the environment variables the CLI reads — add new vars here.
8
+ *
9
+ * Unlike the long-running services (storage-api, ai), a CLI reads config lazily and
10
+ * different commands need different vars, so there is currently NO universally required
11
+ * variable: every entry is optional or carries a default that preserves prior behavior.
12
+ * The fail-fast (❌ + exit 1) path is kept for parity with the services and so that a
13
+ * genuinely-required var added later fails at process start listing every problem.
14
+ */
15
+ exports.envSchema = zod_1.z.object({
16
+ // --- Cloud / auth endpoints ---
17
+ FCODE_CLOUD_URL: zod_1.z.string().min(1).default('https://code.factorial.dev/platform'),
18
+ FCODE_AUTH_URL: zod_1.z.string().min(1).optional(),
19
+ FCODE_CLIENT_ID: zod_1.z.string().min(1).default('cli'),
20
+ // --- Local config location (defaults to os.homedir() at the call site) ---
21
+ FCODE_CONFIG_PATH: zod_1.z.string().min(1).optional(),
22
+ // --- Behavior flags (compared as the strings 'true' / 'false' at the call sites) ---
23
+ IGNORE_WORKSPACE_NOT_EMPTY: zod_1.z.enum(['true', 'false']).optional(),
24
+ FCODE_DISABLE_UPDATE_NOTIFIER: zod_1.z.enum(['true', 'false']).optional(),
25
+ SILENT_MODE: zod_1.z.enum(['true', 'false']).optional(),
26
+ CLI_UX_SKIP_TTY_CHECK: zod_1.z.string().min(1).optional(),
27
+ // --- Debug tooling ---
28
+ YC_COMMAND: zod_1.z.string().min(1).optional(),
29
+ });
30
+ /** Treat empty strings (e.g. an unset shell/dotenv var rendered as "") as missing. */
31
+ const raw = Object.fromEntries(Object.entries(process.env).filter(([, value]) => value !== ''));
32
+ /**
33
+ * Skip the hard fail-fast under test runners and when explicitly opted out, so importing
34
+ * this module never kills a test worker. Real invocations always validate.
35
+ */
36
+ const skipHardExit = Boolean(process.env.VITEST) || process.env.SKIP_ENV_VALIDATION === 'true';
37
+ const result = exports.envSchema.safeParse(raw);
38
+ let parsed;
39
+ if (result.success) {
40
+ parsed = result.data;
41
+ }
42
+ else if (skipHardExit) {
43
+ // No universally-required vars today, so parsing `raw` alone succeeds; this branch stays
44
+ // for parity with the services if a required var is ever added.
45
+ parsed = exports.envSchema.parse(raw);
46
+ }
47
+ else {
48
+ const issues = result.error.issues.map((issue) => ` - ${issue.path.join('.') || '(root)'}: ${issue.message}`).join('\n');
49
+ process.stderr.write(`❌ Invalid environment variables:\n${issues}\n`);
50
+ process.exit(1);
51
+ }
52
+ exports.env = parsed;
53
+ exports.default = exports.env;
@@ -20,14 +20,12 @@ const updateNotifier = async function ({ config, id }) {
20
20
  await globalConfig.set('versionCheckTime', now);
21
21
  if (config.version !== lastestVersion) {
22
22
  console.log(`
23
- ${color_1.default.cyan(`__ __ _____ _
24
- \\ \\ / / / ____| | |
25
- \\ \\_/ /__ _ __ | | ___ __| | ___
26
- \\ / _ \\ '_ \\| | / _ \\ / _\` |/ _ \\
27
- | | __/ |_) | |___| (_) | (_| | __/
28
- |_|\\___| .__/ \\_____\\___/ \\__,_|\\___|
29
- | |
30
- |_|`)}
23
+ ${color_1.default.cyan(` ______ _ _ _ _____ _
24
+ | ____| | | (_) | |/ ____| | |
25
+ | |__ __ _ ___| |_ ___ _ __ _ __ _| | | ___ __| | ___
26
+ | __/ _\` |/ __| __/ _ \\| '__| |/ _\` | | | / _ \\ / _\` |/ _ \\
27
+ | | | (_| | (__| || (_) | | | | (_| | | |___| (_) | (_| | __/
28
+ |_| \\__,_|\\___|\\__\\___/|_| |_|\\__,_|_|\\_____\\___/ \\__,_|\\___|`)}
31
29
 
32
30
  Update available ${color_1.default.red(config.version)} → ${color_1.default.greenBright(lastestVersion)}
33
31
  Run ${color_1.default.bold('`npm i -g @factorialco/fcode-cli`')} to update
@@ -90,6 +90,25 @@ class FcodeGlobal:
90
90
  self.variables = variables
91
91
  self.processes = Processes()
92
92
 
93
+ def send_mail(self, to=None, subject=None, text=None, html=None):
94
+ # No manager locally: log the email contents instead of sending, so devs
95
+ # can see exactly what would be delivered when run in the cloud.
96
+ import json
97
+ import logging
98
+
99
+ logger = logging.getLogger("fcode_logger")
100
+ logger.info(
101
+ "[fcode] send_mail (local run) — not sent, logging contents below:"
102
+ )
103
+ logger.info(
104
+ json.dumps(
105
+ {"to": to, "subject": subject, "text": text, "html": html},
106
+ indent=2,
107
+ )
108
+ )
109
+ accepted = to if isinstance(to, list) else ([to] if to else [])
110
+ return {"messageId": "local-run", "accepted": accepted, "rejected": []}
111
+
93
112
  def import_module(self, module_name, version=None):
94
113
  module_path = module_name
95
114
  if version:
@@ -99,7 +99,7 @@ const AuthService = {
99
99
  client_id: this.clientId,
100
100
  refresh_token: authInfo.token.refresh_token,
101
101
  });
102
- await (0, node_fetch_1.default)(`${url}/logout`, {
102
+ await (0, node_fetch_1.default)(`${authInfo.authUrl}${AUTH_URL_PATH}/logout`, {
103
103
  body: body.toString(),
104
104
  headers: {
105
105
  Authorization: `Bearer ${authInfo.token.access_token}`,
@@ -1,2 +1,4 @@
1
- export declare function buildAuthUrl(url: string): string;
1
+ export declare function toOrigin(input: string): string;
2
+ export declare function buildCloudUrl(input: string): string;
3
+ export declare function buildAuthUrl(input: string): string;
2
4
  export declare function isDefaultAuthUrl(authUrl: string, baseUrl: string): boolean;
@@ -1,9 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toOrigin = toOrigin;
4
+ exports.buildCloudUrl = buildCloudUrl;
3
5
  exports.buildAuthUrl = buildAuthUrl;
4
6
  exports.isDefaultAuthUrl = isDefaultAuthUrl;
5
- function buildAuthUrl(url) {
6
- return process.env.FCODE_AUTH_URL || new URL(url).origin + '/auth/realms/fcode';
7
+ const PLATFORM_PATH = '/platform';
8
+ function toUrl(input) {
9
+ const withScheme = /^[a-z]+:\/\//i.test(input) ? input : `https://${input}`;
10
+ return new URL(withScheme);
11
+ }
12
+ function toOrigin(input) {
13
+ return toUrl(input).origin;
14
+ }
15
+ function buildCloudUrl(input) {
16
+ const url = toUrl(input);
17
+ const path = url.pathname.replace(/\/+$/, '');
18
+ if (path !== '' && path !== PLATFORM_PATH) {
19
+ throw new Error(`Invalid Factorial Code URL '${input}'. Provide a host (ie: code.factorialhr.com) or a ${PLATFORM_PATH} URL (ie: https://code.factorialhr.com${PLATFORM_PATH}).`);
20
+ }
21
+ return url.origin + PLATFORM_PATH;
22
+ }
23
+ function buildAuthUrl(input) {
24
+ return process.env.FCODE_AUTH_URL || toOrigin(input) + '/auth/realms/fcode';
7
25
  }
8
26
  function isDefaultAuthUrl(authUrl, baseUrl) {
9
27
  return authUrl === buildAuthUrl(baseUrl);
@@ -141,16 +141,16 @@
141
141
  "flags": {
142
142
  "url": {
143
143
  "char": "u",
144
- "description": "Factorial Code Cloud endpoint",
144
+ "description": "Factorial Code Cloud endpoint (host or full /platform URL, ie: code.factorialhr.com)",
145
145
  "name": "url",
146
- "default": "https://code.factorial.dev/platform",
146
+ "default": "https://code.factorialhr.com/platform",
147
147
  "hasDynamicHelp": false,
148
148
  "multiple": false,
149
149
  "type": "option"
150
150
  },
151
151
  "authUrl": {
152
152
  "char": "a",
153
- "description": "Factorial Code Auth endpoint (including realm, ie: https://code.factorial.dev/auth/realms/fcode)). Only needed if auth domain is different than used cloud endpoint",
153
+ "description": "Factorial Code Auth endpoint (including realm, ie: https://code.factorialhr.com/auth/realms/fcode)). Only needed if auth domain is different than used cloud endpoint",
154
154
  "name": "authUrl",
155
155
  "hasDynamicHelp": false,
156
156
  "multiple": false,
@@ -229,9 +229,9 @@
229
229
  "flags": {
230
230
  "url": {
231
231
  "char": "u",
232
- "description": "Factorial Code Cloud endpoint",
232
+ "description": "Factorial Code Cloud endpoint (host or full /platform URL, ie: code.factorialhr.com)",
233
233
  "name": "url",
234
- "default": "https://code.factorial.dev/platform",
234
+ "default": "https://code.factorialhr.com/platform",
235
235
  "hasDynamicHelp": false,
236
236
  "multiple": false,
237
237
  "type": "option"
@@ -1779,5 +1779,5 @@
1779
1779
  ]
1780
1780
  }
1781
1781
  },
1782
- "version": "1.1.0"
1782
+ "version": "1.2.0"
1783
1783
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@factorialco/fcode-cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "packageManager": "pnpm@11.7.0",
5
5
  "description": "Factorial Code Command Line Interface",
6
- "homepage": "https://code.factorial.dev/docs/cli/",
6
+ "homepage": "https://code.factorialhr.com/docs/cli/",
7
7
  "author": "Factorial Code Developers <factorial-code@factorial.dev>",
8
8
  "license": "See LICENSE file",
9
9
  "keywords": [
@@ -40,7 +40,8 @@
40
40
  "open": "^8.4.2",
41
41
  "openid-client": "^5.6.5",
42
42
  "string-width": "^4.2.3",
43
- "strip-comments": "^2.0.1"
43
+ "strip-comments": "^2.0.1",
44
+ "zod": "^4.4.3"
44
45
  },
45
46
  "peerDependencies": {
46
47
  "graphql-request": "^7.4.0",