@kestra-io/create-artifact-sdk 0.0.1 → 0.0.2

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
@@ -22,10 +22,8 @@ The CLI will:
22
22
  1. **Detect your plugin** — reads `settings.gradle[.kts]` to find the plugin group id (e.g. `io.kestra.plugin.redis`).
23
23
  2. **Ask which task** you want to add a custom UI for (e.g. `list.ListPop`).
24
24
  3. **Ask which UI module** to customize:
25
- - `topology-details` — panel shown when a task node is selected in the topology view
25
+ - `topology-details` — panel shown when a task node is displayed in the topology view
26
26
  - `log-details` — panel shown in the log view for a task execution
27
- - `metrics-details` — panel shown in the metrics tab for a task execution
28
- - `trigger-details` — panel shown when a trigger node is selected
29
27
  4. **Show a summary** and ask for confirmation before writing anything.
30
28
  5. **Scaffold the `ui/` directory** with:
31
29
  - `package.json` (with all required dependencies)
package/bin/create.js CHANGED
@@ -16,23 +16,26 @@ import pc from "picocolors";
16
16
  import path from "node:path";
17
17
  import fs from "node:fs";
18
18
 
19
- import { detectContext } from "../src/context.js";
20
- import { UI_MODULES, deriveComponentName } from "../src/modules.js";
19
+ import { detectContext } from "@kestra-io/artifact-sdk";
21
20
  import {
22
- scaffoldPackageJson,
23
- scaffoldMainTs,
21
+ UI_MODULES,
22
+ deriveComponentName,
23
+ findKestraTasks,
24
+ scaffoldComponent,
25
+ scaffoldStory,
24
26
  scaffoldAppVue,
25
27
  scaffoldViteConfig,
26
- scaffoldComponent,
28
+ } from "@kestra-io/artifact-sdk";
29
+ import {
30
+ scaffoldPackageJson,
31
+ scaffoldMainTs,
27
32
  scaffoldIndexHtml,
28
33
  scaffoldTsConfig,
29
34
  scaffoldStorybookMain,
30
35
  scaffoldStorybookPreview,
31
- scaffoldStory,
32
36
  scaffoldGitignore,
33
37
  } from "../src/scaffold.js";
34
38
  import { npmInstall } from "../src/runner.js";
35
- import { findKestraTasks } from "../src/find-kestra-tasks.js";
36
39
 
37
40
  // ---------------------------------------------------------------------------
38
41
  // Helpers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kestra-io/create-artifact-sdk",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Scaffold a Kestra plugin custom UI package",
5
5
  "keywords": [
6
6
  "kestra",
@@ -11,10 +11,10 @@
11
11
  "license": "MIT",
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "https://github.com/kestra-io/artifact-sdk.git"
14
+ "url": "git+https://github.com/kestra-io/artifact-sdk.git"
15
15
  },
16
16
  "bin": {
17
- "create-artifact-sdk": "./bin/create.js"
17
+ "create-artifact-sdk": "bin/create.js"
18
18
  },
19
19
  "files": [
20
20
  "bin",
package/src/scaffold.js CHANGED
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
- const TEMPLATES_DIR = path.resolve(__dirname, "../templates");
6
+ const STATIC_DIR = path.resolve(__dirname, "../templates/static");
7
7
 
8
8
  // ---------------------------------------------------------------------------
9
9
  // Internal helpers
@@ -15,30 +15,7 @@ function write(filePath, content) {
15
15
  }
16
16
 
17
17
  function readStatic(name) {
18
- return fs.readFileSync(path.join(TEMPLATES_DIR, "static", name), "utf8");
19
- }
20
-
21
- function readDynamic(name) {
22
- return fs.readFileSync(path.join(TEMPLATES_DIR, "dynamic", name), "utf8");
23
- }
24
-
25
- function readStarter(name) {
26
- // get the root of the starters directory, which is in @kestra-io/artifact-sdk
27
- const startersDir = path.dirname(
28
- require.resolve("@kestra-io/artifact-sdk/starters/placeholder.txt"),
29
- );
30
- return fs.readFileSync(path.join(startersDir, name), "utf8");
31
- }
32
-
33
- /**
34
- * Replace all `{{key}}` placeholders in a template string with the
35
- * corresponding values from the `vars` object.
36
- */
37
- function render(template, vars) {
38
- return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
39
- if (!(key in vars)) throw new Error(`Template variable "{{${key}}}" has no value.`);
40
- return vars[key];
41
- });
18
+ return fs.readFileSync(path.join(STATIC_DIR, name), "utf8");
42
19
  }
43
20
 
44
21
  // ---------------------------------------------------------------------------
@@ -105,7 +82,30 @@ export function scaffoldTsConfig(uiDir) {
105
82
  }
106
83
 
107
84
  // ---------------------------------------------------------------------------
108
- // Static files read verbatim from templates/static/
85
+ // index.html (inlinedsingle {{pluginName}} substitution)
86
+ // ---------------------------------------------------------------------------
87
+
88
+ export function scaffoldIndexHtml(uiDir, pluginName) {
89
+ const content = [
90
+ `<!doctype html>`,
91
+ `<html lang="en">`,
92
+ ` <head>`,
93
+ ` <meta charset="UTF-8" />`,
94
+ ` <meta name="viewport" content="width=device-width, initial-scale=1.0" />`,
95
+ ` <title>${pluginName} — Plugin UI Dev</title>`,
96
+ ` </head>`,
97
+ ` <body>`,
98
+ ` <div id="app"></div>`,
99
+ ` <script type="module" src="/src/main.ts"></script>`,
100
+ ` </body>`,
101
+ `</html>`,
102
+ ``,
103
+ ].join("\n");
104
+ write(path.join(uiDir, "index.html"), content);
105
+ }
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Static files — read verbatim from templates
109
109
  // ---------------------------------------------------------------------------
110
110
 
111
111
  export function scaffoldMainTs(uiDir) {
@@ -123,72 +123,3 @@ export function scaffoldStorybookPreview(uiDir) {
123
123
  export function scaffoldGitignore(uiDir) {
124
124
  write(path.join(uiDir, ".gitignore"), readStatic("gitignore"));
125
125
  }
126
-
127
- // ---------------------------------------------------------------------------
128
- // Dynamic files — rendered from templates/dynamic/*.hbs
129
- // ---------------------------------------------------------------------------
130
-
131
- export function scaffoldAppVue(uiDir, componentName, moduleValue) {
132
- const content = render(readDynamic("App.vue.hbs"), {
133
- componentName,
134
- moduleValue,
135
- });
136
- write(path.join(uiDir, "src", "App.vue"), content);
137
- }
138
-
139
- export function scaffoldIndexHtml(uiDir, pluginName) {
140
- const content = render(readDynamic("index.html.hbs"), { pluginName });
141
- write(path.join(uiDir, "index.html"), content);
142
- }
143
-
144
- export function scaffoldViteConfig(
145
- uiDir,
146
- pluginId,
147
- taskType,
148
- moduleValue,
149
- componentName,
150
- additionalProperties,
151
- ) {
152
- const hasAdditional = Object.keys(additionalProperties).length > 0;
153
- const additionalPropertiesStr = hasAdditional
154
- ? "\n additionalProperties: " +
155
- JSON.stringify(additionalProperties, null, 2).split("\n").join("\n ") +
156
- ","
157
- : "";
158
-
159
- const content = render(readDynamic("vite.config.ts.hbs"), {
160
- pluginId,
161
- taskType,
162
- moduleValue,
163
- componentName,
164
- additionalProperties: additionalPropertiesStr,
165
- });
166
- write(path.join(uiDir, "vite.config.ts"), content);
167
- }
168
-
169
- export function scaffoldStory(uiDir, componentName, moduleValue) {
170
- const content = render(readDynamic("Component.stories.ts.hbs"), {
171
- componentName,
172
- moduleValue,
173
- });
174
- write(path.join(uiDir, "src", `${componentName}.stories.ts`), content);
175
- }
176
-
177
- // ---------------------------------------------------------------------------
178
- // Component — copied from templates/starters/ with fallback
179
- // ---------------------------------------------------------------------------
180
-
181
- export function scaffoldComponent(uiDir, componentName, starterFile) {
182
- let content;
183
- try {
184
- content = readStarter(starterFile);
185
- } catch {
186
- // Fallback used before @kestra-io/artifact-sdk is published
187
- const uiType = starterFile.replace(".vue", "");
188
- content = render(readDynamic("Component.fallback.vue.hbs"), {
189
- componentName,
190
- uiType,
191
- });
192
- }
193
- write(path.join(uiDir, "src", "components", `${componentName}.vue`), content);
194
- }
package/src/context.js DELETED
@@ -1,75 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
-
4
- /**
5
- * Walk upward from `startDir` looking for a settings.gradle or settings.gradle.kts file.
6
- * Returns { dir, file, content } or null if not found within `maxLevels`.
7
- */
8
- function findGradleSettings(startDir, maxLevels = 2) {
9
- let dir = startDir;
10
- for (let i = 0; i <= maxLevels; i++) {
11
- for (const name of ["settings.gradle.kts", "settings.gradle"]) {
12
- const candidate = path.join(dir, name);
13
- if (fs.existsSync(candidate)) {
14
- return { dir, file: candidate, content: fs.readFileSync(candidate, "utf8") };
15
- }
16
- }
17
- const parent = path.dirname(dir);
18
- if (parent === dir) break; // filesystem root
19
- dir = parent;
20
- }
21
- return null;
22
- }
23
-
24
- /**
25
- * Extract the rootProject.name value from a Gradle settings file.
26
- * Handles both:
27
- * rootProject.name = "my-plugin" (Groovy)
28
- * rootProject.name = "my-plugin" (KTS)
29
- */
30
- function extractPluginName(content) {
31
- const match = content.match(/rootProject\.name\s*=\s*["']([^"']+)["']/);
32
- return match ? match[1] : null;
33
- }
34
-
35
- /**
36
- * Derive a Java-style plugin group id from the project name.
37
- * e.g. "plugin-redis" → "io.kestra.plugin.redis"
38
- * If the name already looks like a group id, return as-is.
39
- */
40
- function derivePluginId(name) {
41
- if (name.includes(".")) return name;
42
- // strip leading "plugin-" or "kestra-plugin-" if present
43
- const stripped = name.replace(/^(kestra-)?plugin-/i, "");
44
- return `io.kestra.plugin.${stripped}`;
45
- }
46
-
47
- export function detectContext(cwd = process.cwd()) {
48
- const gradle = findGradleSettings(cwd);
49
-
50
- let pluginRoot = null;
51
- let pluginName = null;
52
- let pluginId = null;
53
- let uiDirExists = false;
54
- let gradleFile = null;
55
-
56
- if (gradle) {
57
- pluginRoot = gradle.dir;
58
- gradleFile = gradle.file;
59
- pluginName = extractPluginName(gradle.content);
60
- if (pluginName) {
61
- pluginId = derivePluginId(pluginName);
62
- }
63
- uiDirExists = fs.existsSync(path.join(pluginRoot, "ui"));
64
- }
65
-
66
- return {
67
- cwd,
68
- pluginRoot, // absolute path to the directory containing settings.gradle
69
- gradleFile, // absolute path to the settings.gradle file
70
- pluginName, // raw name from rootProject.name, e.g. "plugin-redis"
71
- pluginId, // derived group id, e.g. "io.kestra.plugin.redis"
72
- uiDirExists, // whether ui/ already exists in pluginRoot
73
- uiDir: pluginRoot ? path.join(pluginRoot, "ui") : null,
74
- };
75
- }
@@ -1,97 +0,0 @@
1
- import { readdir, readFile } from "node:fs/promises";
2
- import { join, extname } from "node:path";
3
-
4
- // ---------------------------------------------------------------------------
5
- // Heuristics (mirrors the GitHub version)
6
- // ---------------------------------------------------------------------------
7
-
8
- const TASK_ANNOTATIONS = [/@Plugin\s*\(/, /@Schema\s*\(/];
9
-
10
- const TASK_SUPERTYPES = [
11
- /implements\s+Task\b/,
12
- /extends\s+\w*(Task|Trigger|Condition)\b/,
13
- /implements\s+RunnableTask/,
14
- /implements\s+FlowableTask/,
15
- /implements\s+TriggerInterface/,
16
- ];
17
-
18
- const EXCLUDE = [
19
- /^\s*(public\s+)?abstract\s+class/m, // abstract classes
20
- /^\s*public\s+interface\s+/m, // interfaces
21
- /package\s+.*\.tests?[.;]/, // test packages
22
- ];
23
-
24
- function isTaskClass(source) {
25
- if (EXCLUDE.some((re) => re.test(source))) return false;
26
- return (
27
- TASK_ANNOTATIONS.some((re) => re.test(source)) || TASK_SUPERTYPES.some((re) => re.test(source))
28
- );
29
- }
30
-
31
- function extractFullClassName(source, filePath) {
32
- const packageMatch = source.match(/^\s*package\s+([\w.]+)\s*;/m);
33
- const pkg = packageMatch ? packageMatch[1] : null;
34
- if (!pkg || !pkg.startsWith("io.kestra.plugin.")) return null;
35
-
36
- const classMatch = source.match(/public\s+(?:final\s+)?class\s+(\w+)/);
37
- const className = classMatch
38
- ? classMatch[1]
39
- : filePath
40
- .split("/")
41
- .pop()
42
- .replace(/\.java$/, "");
43
-
44
- return `${pkg}.${className}`;
45
- }
46
-
47
- // ---------------------------------------------------------------------------
48
- // Recursive .java file collector
49
- // ---------------------------------------------------------------------------
50
-
51
- async function collectJavaFiles(dir, results = []) {
52
- const entries = await readdir(dir, { withFileTypes: true });
53
- await Promise.all(
54
- entries.map((entry) => {
55
- const full = join(dir, entry.name);
56
- if (entry.isDirectory()) return collectJavaFiles(full, results);
57
- if (entry.isFile() && extname(entry.name) === ".java") results.push(full);
58
- }),
59
- );
60
- return results;
61
- }
62
-
63
- // ---------------------------------------------------------------------------
64
- // Main exported function
65
- // ---------------------------------------------------------------------------
66
-
67
- /**
68
- * Scans a local Kestra plugin source tree and returns the class names of all
69
- * concrete task / trigger / condition classes found.
70
- *
71
- * @param {string} srcRoot Path to the source root, e.g. "/path/to/plugin/src"
72
- * or the deeper Java root like "…/src/main/java".
73
- * The function accepts either — it will descend into
74
- * src/main/java automatically if `srcRoot` points at
75
- * the broader `src` directory.
76
- * @returns {Promise<string[]>} Sorted array of fully-qualified class names (e.g. `io.kestra.plugin.jdbc.mysql.Query`).
77
- */
78
- export async function findKestraTasks(srcRoot) {
79
- // Normalise: if the caller passed the broad `src` dir, step into main/java.
80
- const javaRoot = srcRoot.endsWith("/src") ? join(srcRoot, "main", "java") : srcRoot;
81
-
82
- const files = await collectJavaFiles(javaRoot);
83
-
84
- const classNames = (
85
- await Promise.all(
86
- files.map(async (filePath) => {
87
- const source = await readFile(filePath, "utf8");
88
- if (!isTaskClass(source)) return null;
89
- return extractFullClassName(source, filePath);
90
- }),
91
- )
92
- )
93
- .filter(Boolean)
94
- .sort();
95
-
96
- return classNames;
97
- }
package/src/modules.js DELETED
@@ -1,26 +0,0 @@
1
- import { UI_MODULES } from "@kestra-io/artifact-sdk";
2
-
3
- /**
4
- * Derive a PascalCase component name from a task type and a UI module.
5
- *
6
- * The task type is the short dotted form: e.g. "list.ListPop"
7
- * The module suffix is e.g. "TopologyDetails"
8
- *
9
- * Steps:
10
- * 1. Split on "." → ["list", "ListPop"]
11
- * 2. PascalCase each segment → ["List", "ListPop"]
12
- * 3. Join + append suffix → "ListListPopTopologyDetails"
13
- */
14
- export function deriveComponentName(taskType, moduleSuffix) {
15
- const segments = taskType.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1));
16
- return segments.join("") + moduleSuffix;
17
- }
18
-
19
- /**
20
- * Look up a UI_MODULES entry by its value string.
21
- */
22
- export function findModule(value) {
23
- return UI_MODULES[value];
24
- }
25
-
26
- export { UI_MODULES };
@@ -1,14 +0,0 @@
1
- <script setup lang="ts">
2
- import {{componentName}} from "./components/{{componentName}}.vue";
3
-
4
- // Default props for local development — adjust to match your task's real output
5
- const props = {
6
- // TODO: populate with realistic sample data for the "{{moduleValue}}" UI
7
- };
8
- </script>
9
-
10
- <template>
11
- <div style="padding: 1rem; font-family: sans-serif">
12
- <{{componentName}} v-bind="props" />
13
- </div>
14
- </template>
@@ -1,15 +0,0 @@
1
- <script setup lang="ts">
2
- // TODO: define props matching the "{{uiType}}" contract
3
- // from @kestra-io/artifact-sdk once the SDK is published.
4
- defineProps<Record<string, unknown>>();
5
- </script>
6
-
7
- <template>
8
- <div class="kestra-custom-ui">
9
- <p>✏️ Customize this component for your task.</p>
10
- </div>
11
- </template>
12
-
13
- <style scoped>
14
- .kestra-custom-ui { padding: 1rem; }
15
- </style>
@@ -1,20 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/vue3";
2
- import {{componentName}} from "./components/{{componentName}}.vue";
3
-
4
- const meta: Meta<typeof {{componentName}}> = {
5
- title: "Plugin UI / {{moduleValue}} / {{componentName}}",
6
- component: {{componentName}},
7
- tags: ["autodocs"],
8
- argTypes: {
9
- // TODO: define argTypes matching your component's props
10
- },
11
- };
12
-
13
- export default meta;
14
- type Story = StoryObj<typeof {{componentName}}>;
15
-
16
- export const Default: Story = {
17
- args: {
18
- // TODO: populate with realistic sample data
19
- },
20
- };
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>{{pluginName}} — Plugin UI Dev</title>
7
- </head>
8
- <body>
9
- <div id="app"></div>
10
- <script type="module" src="/src/main.ts"></script>
11
- </body>
12
- </html>
@@ -1,13 +0,0 @@
1
- import { defaultViteConfig } from "@kestra-io/artifact-sdk";
2
-
3
- export default defaultViteConfig({
4
- plugin: "{{pluginId}}",
5
- exposes: {
6
- "{{taskType}}": [
7
- {
8
- uiModule: "{{moduleValue}}",
9
- path: "./src/components/{{componentName}}.vue",{{additionalProperties}}
10
- },
11
- ],
12
- },
13
- });
File without changes
File without changes