rty-test-cli-package 1.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/bin/forge-app +69 -0
- package/dist/ManifestBuilder.d.ts +52 -0
- package/dist/ManifestBuilder.js +174 -0
- package/dist/ManifestBuilder.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/forge.config.js +667 -0
- package/package.json +41 -0
- package/src/ManifestBuilder.ts +175 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +27 -0
package/bin/forge-app
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const yargs = require('yargs/yargs');
|
|
4
|
+
const { hideBin } = require('yargs/helpers');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
yargs(hideBin(process.argv))
|
|
9
|
+
.command(
|
|
10
|
+
'build-manifest',
|
|
11
|
+
'Generate a Forge manifest from a config file',
|
|
12
|
+
(yargs) => {
|
|
13
|
+
return yargs
|
|
14
|
+
.option('config', {
|
|
15
|
+
alias: 'c',
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Path to config file (forge.config.js or manifest.config.js)',
|
|
18
|
+
})
|
|
19
|
+
.option('output', {
|
|
20
|
+
alias: 'o',
|
|
21
|
+
type: 'string',
|
|
22
|
+
default: 'manifest.yml',
|
|
23
|
+
description: 'Output file path',
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
(args) => {
|
|
27
|
+
const { findConfigFile, generateManifest } = require('../dist/cli');
|
|
28
|
+
|
|
29
|
+
let configPath = args.config;
|
|
30
|
+
|
|
31
|
+
if (!configPath) {
|
|
32
|
+
configPath = findConfigFile(process.cwd());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!configPath) {
|
|
36
|
+
process.stderr.write(
|
|
37
|
+
'Warning: No config file found.\n'
|
|
38
|
+
+ 'Provide --config <path> or create one of: forge.config.js, manifest.config.js\n',
|
|
39
|
+
);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const resolvedConfigPath = path.resolve(configPath);
|
|
44
|
+
|
|
45
|
+
if (!fs.existsSync(resolvedConfigPath)) {
|
|
46
|
+
process.stderr.write(`Error: Config file not found: ${resolvedConfigPath}\n`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const config = require(resolvedConfigPath);
|
|
51
|
+
const configContent = typeof config === 'function' ? config() : config;
|
|
52
|
+
|
|
53
|
+
Promise.resolve(configContent).then((resolved) => {
|
|
54
|
+
const content = resolved.default ?? resolved;
|
|
55
|
+
const result = generateManifest(content);
|
|
56
|
+
const outPath = path.resolve(args.output);
|
|
57
|
+
|
|
58
|
+
fs.writeFileSync(outPath, result.yaml, 'utf8');
|
|
59
|
+
process.stdout.write(`Wrote manifest to ${outPath}\n`);
|
|
60
|
+
}).catch((err) => {
|
|
61
|
+
process.stderr.write(`Error: ${err.message ?? err}\n`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
.demandCommand(1, 'You need to specify a command. Use --help for usage info.')
|
|
67
|
+
.strict()
|
|
68
|
+
.help()
|
|
69
|
+
.parse();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const forgeManifestSchema: z.ZodObject<{
|
|
3
|
+
app: z.ZodObject<{
|
|
4
|
+
id: z.ZodString;
|
|
5
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
6
|
+
id: z.ZodString;
|
|
7
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
10
|
+
modules: z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>;
|
|
11
|
+
permissions: z.ZodUnknown;
|
|
12
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
13
|
+
app: z.ZodObject<{
|
|
14
|
+
id: z.ZodString;
|
|
15
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
16
|
+
id: z.ZodString;
|
|
17
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
18
|
+
id: z.ZodString;
|
|
19
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
20
|
+
modules: z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>;
|
|
21
|
+
permissions: z.ZodUnknown;
|
|
22
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
23
|
+
app: z.ZodObject<{
|
|
24
|
+
id: z.ZodString;
|
|
25
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
26
|
+
id: z.ZodString;
|
|
27
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
28
|
+
id: z.ZodString;
|
|
29
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
30
|
+
modules: z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>;
|
|
31
|
+
permissions: z.ZodUnknown;
|
|
32
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
33
|
+
export type ForgeManifest = z.infer<typeof forgeManifestSchema>;
|
|
34
|
+
export interface BuildContext {
|
|
35
|
+
command: 'build-manifest';
|
|
36
|
+
mode: string;
|
|
37
|
+
env: Record<string, string>;
|
|
38
|
+
}
|
|
39
|
+
export default class ManifestBuilder {
|
|
40
|
+
static findConfigFile(cwd?: string): string | null;
|
|
41
|
+
static resolveConfigFromFile(configPath: string, context: BuildContext): Promise<Record<string, unknown>>;
|
|
42
|
+
static resolveConfigFromTemplate(templatePath: string, env: Record<string, string>): Record<string, unknown>;
|
|
43
|
+
static generateManifest(config: Record<string, unknown>): {
|
|
44
|
+
yaml: string;
|
|
45
|
+
object: ForgeManifest;
|
|
46
|
+
};
|
|
47
|
+
static validateManifest(manifest: unknown): ForgeManifest;
|
|
48
|
+
private static getAllowedEnv;
|
|
49
|
+
private static interpolateString;
|
|
50
|
+
private static interpolateValue;
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const module_1 = require("module");
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
43
|
+
const zod_1 = require("zod");
|
|
44
|
+
const CONFIG_NAMES = [
|
|
45
|
+
'forge.config.js',
|
|
46
|
+
'forge.config.cjs',
|
|
47
|
+
'forge.config.mjs',
|
|
48
|
+
'forge.config.ts',
|
|
49
|
+
'manifest.config.js',
|
|
50
|
+
'manifest.config.cjs',
|
|
51
|
+
'manifest.config.mjs',
|
|
52
|
+
'manifest.config.ts',
|
|
53
|
+
];
|
|
54
|
+
const PLACEHOLDER_REGEX = /\$\{([^}]+)\}/g;
|
|
55
|
+
const FORGE_APP_PREFIX = 'FORGE_APP_';
|
|
56
|
+
const forgeManifestSchema = zod_1.z
|
|
57
|
+
.object({
|
|
58
|
+
app: zod_1.z.object({
|
|
59
|
+
id: zod_1.z.string(),
|
|
60
|
+
}).passthrough(),
|
|
61
|
+
modules: zod_1.z.record(zod_1.z.unknown()).refine((m) => Object.keys(m).length > 0, {
|
|
62
|
+
message: 'modules cannot be empty',
|
|
63
|
+
}),
|
|
64
|
+
permissions: zod_1.z.unknown(),
|
|
65
|
+
})
|
|
66
|
+
.passthrough();
|
|
67
|
+
class ManifestBuilder {
|
|
68
|
+
static findConfigFile(cwd = process.cwd()) {
|
|
69
|
+
const name = CONFIG_NAMES.find((n) => fs.existsSync(path.join(cwd, n)));
|
|
70
|
+
return name ? path.join(cwd, name) : null;
|
|
71
|
+
}
|
|
72
|
+
static async resolveConfigFromFile(configPath, context) {
|
|
73
|
+
const resolvedPath = path.resolve(configPath);
|
|
74
|
+
const ext = path.extname(resolvedPath).toLowerCase();
|
|
75
|
+
const localRequire = (0, module_1.createRequire)(path.join(process.cwd(), '_resolve.js'));
|
|
76
|
+
if (ext === '.ts') {
|
|
77
|
+
try {
|
|
78
|
+
localRequire('ts-node/register');
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
throw new Error('TypeScript config requires ts-node. Install it: npm i -D ts-node');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const absPath = path.isAbsolute(resolvedPath)
|
|
85
|
+
? resolvedPath
|
|
86
|
+
: path.resolve(process.cwd(), resolvedPath);
|
|
87
|
+
const mod = localRequire(absPath);
|
|
88
|
+
const exported = mod.default ?? mod;
|
|
89
|
+
if (typeof exported === 'function') {
|
|
90
|
+
const result = await exported(context);
|
|
91
|
+
return result != null && typeof result === 'object' ? result : {};
|
|
92
|
+
}
|
|
93
|
+
if (typeof exported === 'object' && exported !== null) {
|
|
94
|
+
return exported;
|
|
95
|
+
}
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
static resolveConfigFromTemplate(templatePath, env) {
|
|
99
|
+
const resolvedPath = path.resolve(templatePath);
|
|
100
|
+
const raw = fs.readFileSync(resolvedPath, 'utf8');
|
|
101
|
+
const ext = path.extname(resolvedPath).toLowerCase();
|
|
102
|
+
let parsed;
|
|
103
|
+
if (ext === '.json') {
|
|
104
|
+
parsed = JSON.parse(raw);
|
|
105
|
+
}
|
|
106
|
+
else if (ext === '.yml' || ext === '.yaml') {
|
|
107
|
+
parsed = yaml_1.default.parse(raw);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(raw);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
parsed = yaml_1.default.parse(raw);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const allowed = ManifestBuilder.getAllowedEnv(env);
|
|
118
|
+
return ManifestBuilder.interpolateValue(parsed, allowed);
|
|
119
|
+
}
|
|
120
|
+
static generateManifest(config) {
|
|
121
|
+
const validated = ManifestBuilder.validateManifest(config);
|
|
122
|
+
const yaml = yaml_1.default.stringify(validated);
|
|
123
|
+
return { yaml, object: validated };
|
|
124
|
+
}
|
|
125
|
+
static validateManifest(manifest) {
|
|
126
|
+
const result = forgeManifestSchema.safeParse(manifest);
|
|
127
|
+
if (!result.success) {
|
|
128
|
+
const formatted = result.error.format();
|
|
129
|
+
const message = JSON.stringify(formatted, null, 2);
|
|
130
|
+
throw new Error(`Manifest validation failed:\n${message}`);
|
|
131
|
+
}
|
|
132
|
+
return result.data;
|
|
133
|
+
}
|
|
134
|
+
static getAllowedEnv(env) {
|
|
135
|
+
return Object.entries(env).reduce((acc, [key, value]) => {
|
|
136
|
+
if (key.startsWith(FORGE_APP_PREFIX) && value !== undefined && value !== null) {
|
|
137
|
+
acc[key] = String(value);
|
|
138
|
+
}
|
|
139
|
+
return acc;
|
|
140
|
+
}, {});
|
|
141
|
+
}
|
|
142
|
+
static interpolateString(str, allowed) {
|
|
143
|
+
const unknownPlaceholders = [];
|
|
144
|
+
const result = str.replace(PLACEHOLDER_REGEX, (_, varName) => {
|
|
145
|
+
const trimmed = varName.trim();
|
|
146
|
+
if (trimmed in allowed) {
|
|
147
|
+
return allowed[trimmed];
|
|
148
|
+
}
|
|
149
|
+
unknownPlaceholders.push(trimmed);
|
|
150
|
+
return `\${${trimmed}}`;
|
|
151
|
+
});
|
|
152
|
+
if (unknownPlaceholders.length > 0) {
|
|
153
|
+
process.stderr.write(`[forge-app] Warning: unknown placeholders (not in FORGE_APP_* env): ${unknownPlaceholders.join(', ')}\n`);
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
static interpolateValue(value, allowed) {
|
|
158
|
+
if (typeof value === 'string') {
|
|
159
|
+
return ManifestBuilder.interpolateString(value, allowed);
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(value)) {
|
|
162
|
+
return value.map((item) => ManifestBuilder.interpolateValue(item, allowed));
|
|
163
|
+
}
|
|
164
|
+
if (value !== null && typeof value === 'object') {
|
|
165
|
+
return Object.entries(value).reduce((acc, [k, v]) => {
|
|
166
|
+
acc[k] = ManifestBuilder.interpolateValue(v, allowed);
|
|
167
|
+
return acc;
|
|
168
|
+
}, {});
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.default = ManifestBuilder;
|
|
174
|
+
//# sourceMappingURL=ManifestBuilder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ManifestBuilder.js","sourceRoot":"","sources":["../src/ManifestBuilder.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ManifestBuilder = void 0;
|
|
7
|
+
var ManifestBuilder_1 = require("./ManifestBuilder");
|
|
8
|
+
Object.defineProperty(exports, "ManifestBuilder", { enumerable: true, get: function () { return __importDefault(ManifestBuilder_1).default; } });
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,qDAA+D;AAAtD,mIAAA,OAAO,OAAmB"}
|
package/forge.config.js
ADDED
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
module.exports = ({ mode, env }) => {
|
|
2
|
+
const generateName = (name) => {
|
|
3
|
+
let prefix = '';
|
|
4
|
+
|
|
5
|
+
if (process.env.ENV_NAME !== 'production') {
|
|
6
|
+
prefix = ADDON_PREFIX || '';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return `${prefix} ${name}`.trim();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const FORGE_APP_ID = process.env.TF_VAR_forge_app_id || process.env.FORGE_APP_ID;
|
|
14
|
+
const BASE_URL = process.env.TF_VAR_base_url || process.env.BASE_URL;
|
|
15
|
+
const ADDON_KEY = process.env.TF_VAR_addon_key || process.env.ADDON_KEY;
|
|
16
|
+
const ADDON_PREFIX = process.env.TF_VAR_addon_name || process.env.ADDON_PREFIX;
|
|
17
|
+
const LICENSE_DISABLED = process.env.TF_VAR_license_disable || process.env.LICENSE_DISABLED;
|
|
18
|
+
const ANALYTICS_GTM_ENABLED = process.env.ANALYTICS_GTM_ENABLED;
|
|
19
|
+
const ANALYTICS_ACCOIL_ENABLED = process.env.ANALYTICS_ACCOIL_ENABLED;
|
|
20
|
+
const PAGE_ICON = 'resource:global-page;assets/images/tis-color.svg';
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
app: {
|
|
24
|
+
id: `ari:cloud:ecosystem::app/${FORGE_APP_ID}`,
|
|
25
|
+
connect: {
|
|
26
|
+
key: ADDON_KEY,
|
|
27
|
+
remote: 'connect',
|
|
28
|
+
authentication: 'jwt'
|
|
29
|
+
},
|
|
30
|
+
runtime: {
|
|
31
|
+
name: 'nodejs20.x'
|
|
32
|
+
},
|
|
33
|
+
licensing: {
|
|
34
|
+
enabled: LICENSE_DISABLED !== 'true',
|
|
35
|
+
editionsEnabled: LICENSE_DISABLED !== 'true'
|
|
36
|
+
},
|
|
37
|
+
"storage": {
|
|
38
|
+
"entities": [
|
|
39
|
+
{
|
|
40
|
+
"name": "work-schedules",
|
|
41
|
+
"attributes": {
|
|
42
|
+
"id": { "type": "string" },
|
|
43
|
+
"name": { "type": "string" },
|
|
44
|
+
"createdBy": { "type": "string" },
|
|
45
|
+
"updatedBy": { "type": "string" },
|
|
46
|
+
"createdAt": { "type": "string" },
|
|
47
|
+
"updatedAt": { "type": "string" },
|
|
48
|
+
"schedule": { "type": "any" },
|
|
49
|
+
},
|
|
50
|
+
"indexes": [
|
|
51
|
+
"name"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "custom-fields",
|
|
56
|
+
"attributes": {
|
|
57
|
+
"fieldId": {
|
|
58
|
+
"type": "string"
|
|
59
|
+
},
|
|
60
|
+
"boardId": {
|
|
61
|
+
"type": "integer"
|
|
62
|
+
},
|
|
63
|
+
"workScheduleId": {
|
|
64
|
+
"type": "string"
|
|
65
|
+
},
|
|
66
|
+
"statusIds": {
|
|
67
|
+
"type": "any"
|
|
68
|
+
},
|
|
69
|
+
"trashed": {
|
|
70
|
+
"type": "boolean"
|
|
71
|
+
},
|
|
72
|
+
"createdAt": {
|
|
73
|
+
"type": "string"
|
|
74
|
+
},
|
|
75
|
+
"createdBy": {
|
|
76
|
+
"type": "string"
|
|
77
|
+
},
|
|
78
|
+
"version": {
|
|
79
|
+
"type": "integer"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"indexes": [
|
|
83
|
+
"fieldId",
|
|
84
|
+
"boardId",
|
|
85
|
+
"trashed"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "presets",
|
|
90
|
+
"attributes": {
|
|
91
|
+
"id": {
|
|
92
|
+
"type": "string"
|
|
93
|
+
},
|
|
94
|
+
"name": {
|
|
95
|
+
"type": "string"
|
|
96
|
+
},
|
|
97
|
+
"reportConfig": {
|
|
98
|
+
"type": "any"
|
|
99
|
+
},
|
|
100
|
+
"createdBy": {
|
|
101
|
+
"type": "string"
|
|
102
|
+
},
|
|
103
|
+
"updatedAt": {
|
|
104
|
+
"type": "string"
|
|
105
|
+
},
|
|
106
|
+
"createdAt": {
|
|
107
|
+
"type": "string"
|
|
108
|
+
},
|
|
109
|
+
"isPublic": {
|
|
110
|
+
"type": "boolean"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"indexes": [
|
|
114
|
+
"createdBy",
|
|
115
|
+
"isPublic",
|
|
116
|
+
"name"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "shared-reports",
|
|
121
|
+
"attributes": {
|
|
122
|
+
"id": {
|
|
123
|
+
"type": "string"
|
|
124
|
+
},
|
|
125
|
+
"reportConfig": {
|
|
126
|
+
"type": "any"
|
|
127
|
+
},
|
|
128
|
+
"createdBy": {
|
|
129
|
+
"type": "string"
|
|
130
|
+
},
|
|
131
|
+
"createdAt": {
|
|
132
|
+
"type": "string"
|
|
133
|
+
},
|
|
134
|
+
"lastVisit": {
|
|
135
|
+
"type": "string"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "permissions",
|
|
141
|
+
"attributes": {
|
|
142
|
+
"key": {
|
|
143
|
+
"type": "string"
|
|
144
|
+
},
|
|
145
|
+
"operator": {
|
|
146
|
+
"type": "string"
|
|
147
|
+
},
|
|
148
|
+
"groups": {
|
|
149
|
+
"type": "any"
|
|
150
|
+
},
|
|
151
|
+
"updatedAt": {
|
|
152
|
+
"type": "string"
|
|
153
|
+
},
|
|
154
|
+
"updatedBy": {
|
|
155
|
+
"type": "string"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"indexes": [
|
|
159
|
+
"key",
|
|
160
|
+
"operator",
|
|
161
|
+
"updatedBy"
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
remotes: [
|
|
168
|
+
{
|
|
169
|
+
key: 'connect',
|
|
170
|
+
baseUrl: BASE_URL,
|
|
171
|
+
operations: [
|
|
172
|
+
'storage',
|
|
173
|
+
'compute'
|
|
174
|
+
],
|
|
175
|
+
auth: {
|
|
176
|
+
appSystemToken: {
|
|
177
|
+
enabled: true
|
|
178
|
+
},
|
|
179
|
+
appUserToken: {
|
|
180
|
+
enabled: true
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
storage: {
|
|
184
|
+
inScopeEUD: true
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
modules: {
|
|
189
|
+
"trigger": [
|
|
190
|
+
{
|
|
191
|
+
"key": "app-lifecycle",
|
|
192
|
+
"function": "lifecycle",
|
|
193
|
+
"events": [
|
|
194
|
+
"avi:forge:installed:app",
|
|
195
|
+
"avi:forge:upgraded:app"
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"key": "custom-field-triggers",
|
|
200
|
+
"function": "cf-triggers",
|
|
201
|
+
"events": [
|
|
202
|
+
"avi:jira:deleted:field",
|
|
203
|
+
"avi:jira:restored:field",
|
|
204
|
+
"avi:jira:trashed:field",
|
|
205
|
+
"avi:jira-software:deleted:board"
|
|
206
|
+
],
|
|
207
|
+
"filter": {
|
|
208
|
+
"ignoreSelf": true,
|
|
209
|
+
"onError": "RECEIVE_AND_LOG"
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
// Commented out: issue update trigger temporarily disabled
|
|
213
|
+
// {
|
|
214
|
+
// "key": "cf-issue-updated",
|
|
215
|
+
// "function": "cf-triggers",
|
|
216
|
+
// "events": [
|
|
217
|
+
// "avi:jira:updated:issue"
|
|
218
|
+
// ],
|
|
219
|
+
// "filter": {
|
|
220
|
+
// "ignoreSelf": true,
|
|
221
|
+
// "expression": "!!event.associatedStatuses\n",
|
|
222
|
+
// "onError": "RECEIVE_AND_LOG"
|
|
223
|
+
// }
|
|
224
|
+
// }
|
|
225
|
+
],
|
|
226
|
+
"scheduledTrigger": [
|
|
227
|
+
{
|
|
228
|
+
"key": "cf-hourly-trigger",
|
|
229
|
+
"function": "cf-recompute",
|
|
230
|
+
"interval": "hour"
|
|
231
|
+
}
|
|
232
|
+
],
|
|
233
|
+
"consumer": [
|
|
234
|
+
{
|
|
235
|
+
"key": "cf-queue",
|
|
236
|
+
"queue": "cf-process-queue",
|
|
237
|
+
"function": "cf-compute"
|
|
238
|
+
}
|
|
239
|
+
],
|
|
240
|
+
"jira:customFieldType": [
|
|
241
|
+
{
|
|
242
|
+
"key": "tis-custom-field-type",
|
|
243
|
+
"name": "Time in status field type",
|
|
244
|
+
"description": "A custom field that shows a time in status.",
|
|
245
|
+
"icon": "resource:custom-fields;assets/tis-color.svg",
|
|
246
|
+
"type": "number",
|
|
247
|
+
"readOnly": true,
|
|
248
|
+
"view": {
|
|
249
|
+
"experience": [
|
|
250
|
+
"issue-view"
|
|
251
|
+
],
|
|
252
|
+
"value": {
|
|
253
|
+
"function": "cf-issue-opened"
|
|
254
|
+
},
|
|
255
|
+
"formatter": {
|
|
256
|
+
"expression": "let days = (value - value % 86400) / 86400;\nlet hours = ((value % 86400) - value % 3600) / 3600;\n\ndays > 0 && hours > 0\n ? `${days}d ${hours}h`\n : days > 0\n ? `${days}d`\n : `${hours}h`\n"
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
"edit": {
|
|
260
|
+
"parser": {
|
|
261
|
+
"expression": "let dMatch = value.match(\"([0-9]+)\\s*d\");\nlet hMatch = value.match(\"([0-9]+)\\s*h\");\n\nlet days = dMatch ? Number(dMatch[1]) : 0;\nlet hours = hMatch ? Number(hMatch[1]) : 0;\n\ndays * 86400 + hours * 3600\n"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
],
|
|
266
|
+
'jira:dashboardGadget': [
|
|
267
|
+
{
|
|
268
|
+
key: 'status-distribution-gadget',
|
|
269
|
+
resource: 'project-burnup',
|
|
270
|
+
resolver: {
|
|
271
|
+
function: 'resolver'
|
|
272
|
+
},
|
|
273
|
+
title: generateName('Burndown Status Tracker'),
|
|
274
|
+
description: 'Keep work flowing. Visualize status trends over time and jump straight to the matching work items.',
|
|
275
|
+
thumbnail: 'resource:project-burnup;assets/images/dashboard-thumbnail.svg',
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
'jira:boardAction': [
|
|
279
|
+
{
|
|
280
|
+
key: 'custom-fields',
|
|
281
|
+
resource: 'custom-fields',
|
|
282
|
+
viewportSize: 'medium',
|
|
283
|
+
resolver: {
|
|
284
|
+
function: 'resolver'
|
|
285
|
+
},
|
|
286
|
+
title: `${ADDON_PREFIX} Agile Board`,
|
|
287
|
+
tooltip: `${ADDON_PREFIX} Agile Board`,
|
|
288
|
+
icon: 'resource:custom-fields;assets/tis-color.svg',
|
|
289
|
+
displayConditions: {
|
|
290
|
+
canAdministerJira: true,
|
|
291
|
+
jiraExpression: 'project.style != \'next-gen\'',
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
"jira:projectPage": [
|
|
296
|
+
{
|
|
297
|
+
key: "sprint-report-project-page",
|
|
298
|
+
resource: "sprint-report",
|
|
299
|
+
resolver: {
|
|
300
|
+
function: 'resolver'
|
|
301
|
+
},
|
|
302
|
+
title: `${`${ADDON_PREFIX} `}Sprint Performance Report`,
|
|
303
|
+
layout: "blank",
|
|
304
|
+
icon: 'resource:sprint-report;assets/tis-color.svg',
|
|
305
|
+
}
|
|
306
|
+
],
|
|
307
|
+
'jira:adminPage': [
|
|
308
|
+
{
|
|
309
|
+
key: 'tis-get-started',
|
|
310
|
+
resource: 'get-started',
|
|
311
|
+
title: generateName('Get started'),
|
|
312
|
+
useAsGetStarted: true,
|
|
313
|
+
layout: 'blank',
|
|
314
|
+
resolver: {
|
|
315
|
+
function: 'resolver'
|
|
316
|
+
},
|
|
317
|
+
conditions: [
|
|
318
|
+
'user_is_logged_in'
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
],
|
|
322
|
+
'jira:globalPage': [
|
|
323
|
+
{
|
|
324
|
+
key: 'global-page',
|
|
325
|
+
resource: 'global-page',
|
|
326
|
+
title: ADDON_PREFIX,
|
|
327
|
+
layout: 'blank',
|
|
328
|
+
icon: PAGE_ICON,
|
|
329
|
+
pages: [
|
|
330
|
+
{
|
|
331
|
+
title: 'Main page',
|
|
332
|
+
route: '/main',
|
|
333
|
+
icon: PAGE_ICON,
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
title: 'Work Schedule',
|
|
337
|
+
route: '/work-schedule',
|
|
338
|
+
icon: PAGE_ICON,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
title: 'Permissions',
|
|
342
|
+
route: '/permissions',
|
|
343
|
+
icon: PAGE_ICON,
|
|
344
|
+
}
|
|
345
|
+
],
|
|
346
|
+
resolver: {
|
|
347
|
+
function: 'resolver'
|
|
348
|
+
},
|
|
349
|
+
conditions: [
|
|
350
|
+
'user_is_logged_in'
|
|
351
|
+
]
|
|
352
|
+
}
|
|
353
|
+
],
|
|
354
|
+
'jira:issueActivity': [
|
|
355
|
+
{
|
|
356
|
+
key: 'activity-tab',
|
|
357
|
+
title: ADDON_PREFIX,
|
|
358
|
+
resource: 'activity-tab',
|
|
359
|
+
resolver: {
|
|
360
|
+
function: 'resolver'
|
|
361
|
+
},
|
|
362
|
+
displayConditions: {
|
|
363
|
+
isLoggedIn: true,
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
],
|
|
367
|
+
function: [
|
|
368
|
+
{
|
|
369
|
+
key: 'resolver',
|
|
370
|
+
handler: 'index.handler'
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"key": "cf-triggers",
|
|
374
|
+
"handler": "index.customFieldTriggers"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"key": "cf-issue-opened",
|
|
378
|
+
"handler": "index.issueOpened"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"key": "cf-recompute",
|
|
382
|
+
"handler": "index.recomputeCustomField"
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"key": "cf-compute",
|
|
386
|
+
"handler": "index.computeCustomField",
|
|
387
|
+
"timeoutSeconds": 900
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
"key": "calc-flagged-tis-fn",
|
|
391
|
+
"handler": "index.calculateFlaggedStatusTime"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"key": "lifecycle",
|
|
395
|
+
"handler": "index.lifecycle"
|
|
396
|
+
}
|
|
397
|
+
],
|
|
398
|
+
action: [
|
|
399
|
+
{
|
|
400
|
+
"key": "calc-flagged-tis",
|
|
401
|
+
"name": "Calculate Flagged Status Time",
|
|
402
|
+
"function": "calc-flagged-tis-fn",
|
|
403
|
+
"actionVerb": "GET",
|
|
404
|
+
"description": "Calculate the time work items spent in each status while flagged",
|
|
405
|
+
"inputs": {
|
|
406
|
+
"jql": {
|
|
407
|
+
"title": "JQL Query",
|
|
408
|
+
"type": "string",
|
|
409
|
+
"required": false,
|
|
410
|
+
"description": "JQL query to filter work items"
|
|
411
|
+
},
|
|
412
|
+
"filterId": {
|
|
413
|
+
"title": "Filter Id",
|
|
414
|
+
"type": "string",
|
|
415
|
+
"required": false,
|
|
416
|
+
"description": "Filter Id to filter work items"
|
|
417
|
+
},
|
|
418
|
+
"filterName": {
|
|
419
|
+
"title": "Filter Name",
|
|
420
|
+
"type": "string",
|
|
421
|
+
"required": false,
|
|
422
|
+
"description": "Filter Name to filter work items"
|
|
423
|
+
},
|
|
424
|
+
"from": {
|
|
425
|
+
"title": "From Date",
|
|
426
|
+
"type": "string",
|
|
427
|
+
"required": false,
|
|
428
|
+
"description": "Start date (ISO 8601)"
|
|
429
|
+
},
|
|
430
|
+
"to": {
|
|
431
|
+
"title": "To Date",
|
|
432
|
+
"type": "string",
|
|
433
|
+
"required": false,
|
|
434
|
+
"description": "End date (ISO 8601)"
|
|
435
|
+
},
|
|
436
|
+
"timezone": {
|
|
437
|
+
"title": "Timezone",
|
|
438
|
+
"type": "string",
|
|
439
|
+
"required": false,
|
|
440
|
+
"description": "IANA timezone (e.g., America/New_York)"
|
|
441
|
+
},
|
|
442
|
+
"excludedStatuses": {
|
|
443
|
+
"title": "Excluded Statuses (CSV)",
|
|
444
|
+
"type": "string",
|
|
445
|
+
"required": false,
|
|
446
|
+
"description": "Comma-separated list of statuses to exclude"
|
|
447
|
+
},
|
|
448
|
+
"targetStatuses": {
|
|
449
|
+
"title": "Target Statuses (CSV)",
|
|
450
|
+
"type": "string",
|
|
451
|
+
"required": false,
|
|
452
|
+
"description": "Comma-separated list of statuses to include"
|
|
453
|
+
},
|
|
454
|
+
"maxResults": {
|
|
455
|
+
"title": "Max Results",
|
|
456
|
+
"type": "integer",
|
|
457
|
+
"required": false,
|
|
458
|
+
"description": "Maximum number of issues to process"
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
],
|
|
463
|
+
"rovo:agent": [
|
|
464
|
+
{
|
|
465
|
+
key: "time-in-status-agent",
|
|
466
|
+
name: `FlagFocus by ${ADDON_PREFIX}`,
|
|
467
|
+
icon: "resource:forge-static;rovo-agent-logo.svg",
|
|
468
|
+
description:
|
|
469
|
+
"From overview to action: totals, breakdowns, and takeaways that help teams unstick flagged work quickly.",
|
|
470
|
+
prompt: `resource:rovoPrompts;flaggedAgent/prompt.txt`,
|
|
471
|
+
conversationStarters: [
|
|
472
|
+
"Tell me about yourself",
|
|
473
|
+
"Count flagged work items for the last 30 days",
|
|
474
|
+
"What’s the flagged time for today?"
|
|
475
|
+
],
|
|
476
|
+
actions: ["calc-flagged-tis"]
|
|
477
|
+
}
|
|
478
|
+
]
|
|
479
|
+
},
|
|
480
|
+
resources: [
|
|
481
|
+
{
|
|
482
|
+
key: 'custom-fields',
|
|
483
|
+
path: '../custom-fields/dist',
|
|
484
|
+
tunnel: {
|
|
485
|
+
port: 3002
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
key: 'forge-static',
|
|
490
|
+
path: 'static',
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
key: 'global-page',
|
|
494
|
+
path: '../global-page/dist',
|
|
495
|
+
tunnel: {
|
|
496
|
+
port: 3003
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
key: 'get-started',
|
|
501
|
+
path: '../get-started/dist',
|
|
502
|
+
tunnel: {
|
|
503
|
+
port: 3001
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
key: "sprint-report",
|
|
508
|
+
path: '../sprint-report/dist',
|
|
509
|
+
tunnel: {
|
|
510
|
+
port: 5173
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
key: "rovoPrompts",
|
|
515
|
+
path: './rovo',
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
key: 'activity-tab',
|
|
519
|
+
path: '../activity-tab/dist',
|
|
520
|
+
tunnel: {
|
|
521
|
+
port: 3005
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
key: 'project-burnup',
|
|
526
|
+
path: '../dashboard-project-burnup/dist',
|
|
527
|
+
tunnel: {
|
|
528
|
+
port: 3004
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
connectModules: {
|
|
533
|
+
'jira:lifecycle': [
|
|
534
|
+
{
|
|
535
|
+
key: 'lifecycle-events',
|
|
536
|
+
installed: '/installed?v=3.32.0',
|
|
537
|
+
uninstalled: '/uninstalled'
|
|
538
|
+
}
|
|
539
|
+
],
|
|
540
|
+
'jira:jiraDashboardItems': [
|
|
541
|
+
{
|
|
542
|
+
name: {
|
|
543
|
+
value: generateName('Accurate Time in Status'),
|
|
544
|
+
},
|
|
545
|
+
url: '/dashboard-gadget-tis?dashboard={dashboard.id}&dashboardItem={dashboardItem.id}',
|
|
546
|
+
cacheable: true,
|
|
547
|
+
key: 'dashboard-gadget-tis',
|
|
548
|
+
description: {
|
|
549
|
+
value: 'Track time issues spend in each status. Identify delays with charts and lists. Boost efficiency now.'
|
|
550
|
+
},
|
|
551
|
+
thumbnailUrl: '/images/dashboard-thumbnail.svg',
|
|
552
|
+
configurable: true,
|
|
553
|
+
conditions: [
|
|
554
|
+
{
|
|
555
|
+
condition: 'user_is_logged_in'
|
|
556
|
+
}
|
|
557
|
+
]
|
|
558
|
+
}
|
|
559
|
+
],
|
|
560
|
+
'jira:webhooks': [
|
|
561
|
+
{
|
|
562
|
+
event: 'board_deleted',
|
|
563
|
+
url: '/webhooks',
|
|
564
|
+
key: 'webhook-1'
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
event: 'sprint_closed',
|
|
568
|
+
url: '/webhooks',
|
|
569
|
+
key: 'webhook-2'
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
event: 'board_configuration_changed',
|
|
573
|
+
url: '/webhooks',
|
|
574
|
+
key: 'webhook-3'
|
|
575
|
+
}
|
|
576
|
+
],
|
|
577
|
+
'jira:generalPages': [
|
|
578
|
+
{
|
|
579
|
+
key: 'tis-access-settings',
|
|
580
|
+
location: 'admin_plugins_menu/example-menu-section',
|
|
581
|
+
name: {
|
|
582
|
+
value: generateName('Time in Status Permissions'),
|
|
583
|
+
},
|
|
584
|
+
url: '/configuration-access',
|
|
585
|
+
conditions: [
|
|
586
|
+
{
|
|
587
|
+
condition: 'user_is_logged_in'
|
|
588
|
+
}
|
|
589
|
+
]
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
key: 'tis-calendar-settings',
|
|
593
|
+
location: 'admin_plugins_menu/example-menu-section',
|
|
594
|
+
name: {
|
|
595
|
+
value: generateName('Time in Status Work Calendars'),
|
|
596
|
+
},
|
|
597
|
+
url: '/configuration-calendar',
|
|
598
|
+
conditions: [
|
|
599
|
+
{
|
|
600
|
+
condition: 'user_is_logged_in'
|
|
601
|
+
}
|
|
602
|
+
]
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
key: 'get-started',
|
|
606
|
+
location: 'admin_plugins_menu/example-menu-section',
|
|
607
|
+
name: {
|
|
608
|
+
value: generateName('Get started'),
|
|
609
|
+
},
|
|
610
|
+
url: '/pages/get-started',
|
|
611
|
+
conditions: [
|
|
612
|
+
{
|
|
613
|
+
condition: 'user_is_logged_in'
|
|
614
|
+
}
|
|
615
|
+
]
|
|
616
|
+
}
|
|
617
|
+
],
|
|
618
|
+
'jira:webSections': [
|
|
619
|
+
{
|
|
620
|
+
key: 'example-menu-section',
|
|
621
|
+
location: 'admin_plugins_menu',
|
|
622
|
+
name: {
|
|
623
|
+
value: ADDON_PREFIX
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
]
|
|
627
|
+
},
|
|
628
|
+
permissions: {
|
|
629
|
+
scopes: {
|
|
630
|
+
"read:board-scope.admin:jira-software": {
|
|
631
|
+
"allowImpersonation": true
|
|
632
|
+
},
|
|
633
|
+
"read:project:jira": {
|
|
634
|
+
"allowImpersonation": true
|
|
635
|
+
},
|
|
636
|
+
"read:board-scope:jira-software": {
|
|
637
|
+
"allowImpersonation": true
|
|
638
|
+
},
|
|
639
|
+
"read:issue-details:jira": {
|
|
640
|
+
"allowImpersonation": true
|
|
641
|
+
},
|
|
642
|
+
"manage:jira-configuration": {},
|
|
643
|
+
"read:app-system-token": {},
|
|
644
|
+
"read:app-user-token": {},
|
|
645
|
+
"read:jira-user": {},
|
|
646
|
+
"read:jira-work": {},
|
|
647
|
+
"read:chat:rovo": {},
|
|
648
|
+
"read:sprint:jira-software": {},
|
|
649
|
+
"storage:app": {},
|
|
650
|
+
"write:app-data:jira": {},
|
|
651
|
+
"write:board-scope:jira-software": {},
|
|
652
|
+
"write:jira-work": {},
|
|
653
|
+
"write:project:jira": {},
|
|
654
|
+
"write:sprint:jira-software": {},
|
|
655
|
+
|
|
656
|
+
"read:connect-jira": {},
|
|
657
|
+
"act-as-user:connect-jira": {},
|
|
658
|
+
"write:connect-jira": {},
|
|
659
|
+
},
|
|
660
|
+
content: {
|
|
661
|
+
styles: [
|
|
662
|
+
'unsafe-inline'
|
|
663
|
+
]
|
|
664
|
+
},
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rty-test-cli-package",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A TypeScript CLI package",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/TymchukRoman/-rty-test-cli-package"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"forge-app": "bin/forge-app"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"build": "tsc"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20.0.0"
|
|
20
|
+
},
|
|
21
|
+
"author": "Roman Tymchuk <roman.t@saasjet.com>",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/jest": "^29.5.12",
|
|
24
|
+
"@types/yargs": "^17.0.33",
|
|
25
|
+
"@typescript-eslint/eslint-plugin": "^8.41.0",
|
|
26
|
+
"@typescript-eslint/parser": "^8.41.0",
|
|
27
|
+
"eslint-plugin-import-x": "^4.16.1",
|
|
28
|
+
"eslint": "^9.34.0",
|
|
29
|
+
"eslint-plugin-jest": "^29.0.1",
|
|
30
|
+
"jest": "^29.7.0",
|
|
31
|
+
"rimraf": "^5.0.10",
|
|
32
|
+
"ts-jest": "^29.2.6",
|
|
33
|
+
"typescript": "^5.9.2"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"yaml": "^2.7.0",
|
|
37
|
+
"yargs": "^17.7.2",
|
|
38
|
+
"zod": "^3.24.2"
|
|
39
|
+
},
|
|
40
|
+
"keywords": []
|
|
41
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
import YAML from 'yaml';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
const CONFIG_NAMES = [
|
|
9
|
+
'forge.config.js',
|
|
10
|
+
'forge.config.cjs',
|
|
11
|
+
'forge.config.mjs',
|
|
12
|
+
'forge.config.ts',
|
|
13
|
+
'manifest.config.js',
|
|
14
|
+
'manifest.config.cjs',
|
|
15
|
+
'manifest.config.mjs',
|
|
16
|
+
'manifest.config.ts',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const PLACEHOLDER_REGEX = /\$\{([^}]+)\}/g;
|
|
20
|
+
const FORGE_APP_PREFIX = 'FORGE_APP_';
|
|
21
|
+
|
|
22
|
+
const forgeManifestSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
app: z.object({
|
|
25
|
+
id: z.string(),
|
|
26
|
+
}).passthrough(),
|
|
27
|
+
modules: z.record(z.unknown()).refine((m) => Object.keys(m).length > 0, {
|
|
28
|
+
message: 'modules cannot be empty',
|
|
29
|
+
}),
|
|
30
|
+
permissions: z.unknown(),
|
|
31
|
+
})
|
|
32
|
+
.passthrough();
|
|
33
|
+
|
|
34
|
+
export type ForgeManifest = z.infer<typeof forgeManifestSchema>;
|
|
35
|
+
|
|
36
|
+
export interface BuildContext {
|
|
37
|
+
command: 'build-manifest';
|
|
38
|
+
mode: string;
|
|
39
|
+
env: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default class ManifestBuilder {
|
|
43
|
+
static findConfigFile(cwd: string = process.cwd()): string | null {
|
|
44
|
+
const name = CONFIG_NAMES.find((n) => fs.existsSync(path.join(cwd, n)));
|
|
45
|
+
return name ? path.join(cwd, name) : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static async resolveConfigFromFile(
|
|
49
|
+
configPath: string,
|
|
50
|
+
context: BuildContext,
|
|
51
|
+
): Promise<Record<string, unknown>> {
|
|
52
|
+
const resolvedPath = path.resolve(configPath);
|
|
53
|
+
const ext = path.extname(resolvedPath).toLowerCase();
|
|
54
|
+
const localRequire = createRequire(path.join(process.cwd(), '_resolve.js'));
|
|
55
|
+
|
|
56
|
+
if (ext === '.ts') {
|
|
57
|
+
try {
|
|
58
|
+
localRequire('ts-node/register');
|
|
59
|
+
} catch {
|
|
60
|
+
throw new Error(
|
|
61
|
+
'TypeScript config requires ts-node. Install it: npm i -D ts-node',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const absPath = path.isAbsolute(resolvedPath)
|
|
67
|
+
? resolvedPath
|
|
68
|
+
: path.resolve(process.cwd(), resolvedPath);
|
|
69
|
+
|
|
70
|
+
const mod = localRequire(absPath);
|
|
71
|
+
const exported = mod.default ?? mod;
|
|
72
|
+
|
|
73
|
+
if (typeof exported === 'function') {
|
|
74
|
+
const result = await exported(context);
|
|
75
|
+
return result != null && typeof result === 'object' ? result : {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (typeof exported === 'object' && exported !== null) {
|
|
79
|
+
return exported as Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static resolveConfigFromTemplate(
|
|
86
|
+
templatePath: string,
|
|
87
|
+
env: Record<string, string>,
|
|
88
|
+
): Record<string, unknown> {
|
|
89
|
+
const resolvedPath = path.resolve(templatePath);
|
|
90
|
+
const raw = fs.readFileSync(resolvedPath, 'utf8');
|
|
91
|
+
const ext = path.extname(resolvedPath).toLowerCase();
|
|
92
|
+
|
|
93
|
+
let parsed: unknown;
|
|
94
|
+
if (ext === '.json') {
|
|
95
|
+
parsed = JSON.parse(raw);
|
|
96
|
+
} else if (ext === '.yml' || ext === '.yaml') {
|
|
97
|
+
parsed = YAML.parse(raw);
|
|
98
|
+
} else {
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(raw);
|
|
101
|
+
} catch {
|
|
102
|
+
parsed = YAML.parse(raw);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const allowed = ManifestBuilder.getAllowedEnv(env);
|
|
107
|
+
return ManifestBuilder.interpolateValue(parsed, allowed) as Record<string, unknown>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static generateManifest(
|
|
111
|
+
config: Record<string, unknown>,
|
|
112
|
+
): { yaml: string; object: ForgeManifest } {
|
|
113
|
+
const validated = ManifestBuilder.validateManifest(config);
|
|
114
|
+
const yaml = YAML.stringify(validated);
|
|
115
|
+
return { yaml, object: validated };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static validateManifest(manifest: unknown): ForgeManifest {
|
|
119
|
+
const result = forgeManifestSchema.safeParse(manifest);
|
|
120
|
+
if (!result.success) {
|
|
121
|
+
const formatted = result.error.format();
|
|
122
|
+
const message = JSON.stringify(formatted, null, 2);
|
|
123
|
+
throw new Error(`Manifest validation failed:\n${message}`);
|
|
124
|
+
}
|
|
125
|
+
return result.data;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private static getAllowedEnv(env: Record<string, string>): Record<string, string> {
|
|
129
|
+
return Object.entries(env).reduce<Record<string, string>>((acc, [key, value]) => {
|
|
130
|
+
if (key.startsWith(FORGE_APP_PREFIX) && value !== undefined && value !== null) {
|
|
131
|
+
acc[key] = String(value);
|
|
132
|
+
}
|
|
133
|
+
return acc;
|
|
134
|
+
}, {});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private static interpolateString(str: string, allowed: Record<string, string>): string {
|
|
138
|
+
const unknownPlaceholders: string[] = [];
|
|
139
|
+
const result = str.replace(PLACEHOLDER_REGEX, (_, varName: string) => {
|
|
140
|
+
const trimmed = varName.trim();
|
|
141
|
+
if (trimmed in allowed) {
|
|
142
|
+
return allowed[trimmed];
|
|
143
|
+
}
|
|
144
|
+
unknownPlaceholders.push(trimmed);
|
|
145
|
+
return `\${${trimmed}}`;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (unknownPlaceholders.length > 0) {
|
|
149
|
+
process.stderr.write(
|
|
150
|
+
`[forge-app] Warning: unknown placeholders (not in FORGE_APP_* env): ${unknownPlaceholders.join(', ')}\n`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private static interpolateValue(value: unknown, allowed: Record<string, string>): unknown {
|
|
158
|
+
if (typeof value === 'string') {
|
|
159
|
+
return ManifestBuilder.interpolateString(value, allowed);
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(value)) {
|
|
162
|
+
return value.map((item) => ManifestBuilder.interpolateValue(item, allowed));
|
|
163
|
+
}
|
|
164
|
+
if (value !== null && typeof value === 'object') {
|
|
165
|
+
return Object.entries(value as Record<string, unknown>).reduce<Record<string, unknown>>(
|
|
166
|
+
(acc, [k, v]) => {
|
|
167
|
+
acc[k] = ManifestBuilder.interpolateValue(v, allowed);
|
|
168
|
+
return acc;
|
|
169
|
+
},
|
|
170
|
+
{},
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
}
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowSyntheticDefaultImports": true,
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"resolveJsonModule": true,
|
|
7
|
+
"target": "es2021",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"outDir": "./dist",
|
|
13
|
+
"baseUrl": "./",
|
|
14
|
+
"jsx": "react",
|
|
15
|
+
"esModuleInterop": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
},
|
|
18
|
+
"exclude": [
|
|
19
|
+
"node_modules",
|
|
20
|
+
"../../node_modules",
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"include": [
|
|
24
|
+
"src/**/*",
|
|
25
|
+
".d.ts",
|
|
26
|
+
]
|
|
27
|
+
}
|