stackloom-cli 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.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -0
  3. package/bin/cli.js +306 -0
  4. package/branding.json +8 -0
  5. package/package.json +72 -0
  6. package/src/__tests__/cli-smoke.test.js +46 -0
  7. package/src/blueprint/__tests__/blueprint.test.js +116 -0
  8. package/src/blueprint/blueprint.js +181 -0
  9. package/src/blueprint/default.blueprint.json +78 -0
  10. package/src/blueprint/index.js +10 -0
  11. package/src/blueprint/loader.js +101 -0
  12. package/src/blueprint/schema-kit.js +161 -0
  13. package/src/blueprint/schema.js +78 -0
  14. package/src/branding/__tests__/branding.test.js +49 -0
  15. package/src/branding/index.js +48 -0
  16. package/src/commands/__tests__/commands.test.js +83 -0
  17. package/src/commands/check.js +71 -0
  18. package/src/commands/cleanup.js +347 -0
  19. package/src/commands/customize.js +263 -0
  20. package/src/commands/doctor.js +84 -0
  21. package/src/commands/env.js +75 -0
  22. package/src/commands/finalize.js +68 -0
  23. package/src/commands/generate/ci-cd.js +378 -0
  24. package/src/commands/generate/deploy-advanced.js +253 -0
  25. package/src/commands/generate/deploy.js +99 -0
  26. package/src/commands/generate/env.template.js +221 -0
  27. package/src/commands/generate/index.js +7 -0
  28. package/src/commands/generate/module.js +836 -0
  29. package/src/commands/generate/page.js +1415 -0
  30. package/src/commands/generate/test-scaffold.js +279 -0
  31. package/src/commands/generate/theme.js +67 -0
  32. package/src/commands/generate-resource.js +133 -0
  33. package/src/commands/index.js +9 -0
  34. package/src/commands/init.js +350 -0
  35. package/src/commands/make/resource.js +298 -0
  36. package/src/commands/preset.js +57 -0
  37. package/src/commands/remove.js +170 -0
  38. package/src/commands/rename.js +54 -0
  39. package/src/commands/rollback.js +90 -0
  40. package/src/commands/wizard.js +303 -0
  41. package/src/core/__tests__/generator.test.js +67 -0
  42. package/src/core/__tests__/marker-strategy.test.js +57 -0
  43. package/src/core/__tests__/resource-definition.test.js +32 -0
  44. package/src/core/generator.js +542 -0
  45. package/src/core/marker-strategy.js +138 -0
  46. package/src/core/resource-definition.js +346 -0
  47. package/src/core/state-tracker.js +67 -0
  48. package/src/core/template-loader.js +163 -0
  49. package/src/engine/__tests__/engine.test.js +306 -0
  50. package/src/engine/index.js +21 -0
  51. package/src/engine/injector.js +198 -0
  52. package/src/engine/pipeline.js +138 -0
  53. package/src/engine/transaction.js +105 -0
  54. package/src/engine/validator.js +190 -0
  55. package/src/index.js +4 -0
  56. package/src/recipes/__tests__/recipe.test.js +128 -0
  57. package/src/recipes/builtin/module.json +22 -0
  58. package/src/recipes/builtin/page.json +21 -0
  59. package/src/recipes/builtin/resource.json +35 -0
  60. package/src/recipes/condition.js +147 -0
  61. package/src/recipes/index.js +11 -0
  62. package/src/recipes/loader.js +95 -0
  63. package/src/recipes/recipe.js +89 -0
  64. package/src/recipes/schema.js +47 -0
  65. package/src/schemas/__tests__/schemas.test.js +67 -0
  66. package/src/schemas/index.js +18 -0
  67. package/src/schemas/options.js +38 -0
  68. package/src/schemas/resource.js +112 -0
  69. package/src/services/__tests__/reporter.test.js +98 -0
  70. package/src/services/clock.js +31 -0
  71. package/src/services/index.js +43 -0
  72. package/src/services/reporter.js +136 -0
  73. package/src/templates/resource/api.js.ejs +39 -0
  74. package/src/templates/resource/components/form.jsx.ejs +81 -0
  75. package/src/templates/resource/components/table.jsx.ejs +68 -0
  76. package/src/templates/resource/controller.js.ejs +154 -0
  77. package/src/templates/resource/hooks.js.ejs +46 -0
  78. package/src/templates/resource/model.js.ejs +64 -0
  79. package/src/templates/resource/page-detail.jsx.ejs +55 -0
  80. package/src/templates/resource/page-form.jsx.ejs +30 -0
  81. package/src/templates/resource/page-inline.jsx.ejs +74 -0
  82. package/src/templates/resource/page-modal.jsx.ejs +98 -0
  83. package/src/templates/resource/page-page.jsx.ejs +99 -0
  84. package/src/templates/resource/page-sidepanel.jsx.ejs +100 -0
  85. package/src/templates/resource/routes.js.ejs +35 -0
  86. package/src/templates/resource/service.js.ejs +132 -0
  87. package/src/templates/resource/test.ejs +71 -0
  88. package/src/templates/resource/types.ts.ejs +17 -0
  89. package/src/templates/resource/validator.js.ejs +26 -0
  90. package/src/templates/snippets/lazy-import.ejs +1 -0
  91. package/src/templates/snippets/nav-entry.ejs +1 -0
  92. package/src/templates/snippets/route-entry.ejs +5 -0
  93. package/src/templates/snippets/route-mount.ejs +1 -0
  94. package/src/utils/fieldValidators.js +371 -0
  95. package/src/utils/logging/logger.js +47 -0
  96. package/src/utils/namingUtils.js +38 -0
  97. package/src/utils/sanitize.js +200 -0
@@ -0,0 +1,38 @@
1
+
2
+ export function toCamelCase(str) {
3
+ return str.replace(/([-_\s][a-z])/ig, ($1) => {
4
+ return $1.toUpperCase()
5
+ .replace('-', '')
6
+ .replace('_', '')
7
+ .replace(' ', '');
8
+ });
9
+ }
10
+
11
+ export function toPascalCase(str) {
12
+ const camel = toCamelCase(str);
13
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
14
+ }
15
+
16
+ export function toSnakeCase(str) {
17
+ return str.replace(/([A-Z])/g, "_$1").toLowerCase();
18
+ }
19
+
20
+ export function toKebabCase(str) {
21
+ return str.replace(/([A-Z])/g, "-$1").toLowerCase();
22
+ }
23
+
24
+ export function detectCase(str) {
25
+ if (/^[a-z]+([A-Z][a-z0-9]+)*$/.test(str)) {
26
+ return 'camelCase';
27
+ }
28
+ if (/^[A-Z]+([A-Z][a-z0-9]+)*$/.test(str)) {
29
+ return 'PascalCase';
30
+ }
31
+ if (/^[a-z]+(_[a-z0-9]+)*$/.test(str)) {
32
+ return 'snake_case';
33
+ }
34
+ if (/^[a-z]+(-[a-z0-9]+)*$/.test(str)) {
35
+ return 'kebab-case';
36
+ }
37
+ return 'unknown';
38
+ }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Input Sanitization Utilities
3
+ * Production-ready helpers for preventing XSS, injection, data pollution
4
+ */
5
+
6
+ /**
7
+ * Strip HTML tags from text
8
+ */
9
+ export function sanitizeText(value) {
10
+ if (typeof value !== "string") return value;
11
+ return value.replace(/<[^>]*>?/gm, "").trim();
12
+ }
13
+
14
+ /**
15
+ * Sanitize HTML - allow only safe whitelist
16
+ */
17
+ export function sanitizeHTML(value) {
18
+ if (typeof value !== "string") return value;
19
+
20
+ // Remove scripts, event handlers, javascript: URLs
21
+ const dangerous = [
22
+ /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
23
+ /on\w+="[^"]*"/gi,
24
+ /javascript:/gi,
25
+ /data:/gi,
26
+ ];
27
+
28
+ let cleaned = value;
29
+ dangerous.forEach(pattern => {
30
+ cleaned = cleaned.replace(pattern, "");
31
+ });
32
+
33
+ return cleaned.trim();
34
+ }
35
+
36
+ /**
37
+ * Normalize email to lowercase, trim whitespace
38
+ */
39
+ export function sanitizeEmail(value) {
40
+ if (typeof value !== "string") return value;
41
+ return value.toLowerCase().trim();
42
+ }
43
+
44
+ /**
45
+ * Validate and normalize URL
46
+ */
47
+ export function sanitizeUrl(value) {
48
+ if (typeof value !== "string") return value;
49
+ const trimmed = value.trim();
50
+ // Ensure http/https protocol
51
+ if (!/^https?:\/\//.test(trimmed)) {
52
+ return "https://" + trimmed;
53
+ }
54
+ return trimmed;
55
+ }
56
+
57
+ /**
58
+ * Phone sanitization — E.164 format (removes all non-digit except leading +)
59
+ */
60
+ export function sanitizePhone(value) {
61
+ if (typeof value !== "string") return value;
62
+ const cleaned = value.replace(/[^+0-9]/g, "");
63
+ // Already starts with + and country code?
64
+ if (!/^[+]?[1-9]/.test(cleaned)) {
65
+ // Remove leading zeros and ensure proper format
66
+ return cleaned.replace(/^0+/, "");
67
+ }
68
+ return cleaned;
69
+ }
70
+
71
+ /**
72
+ * Number sanitization — parseFloat with sanitization
73
+ */
74
+ export function sanitizeNumber(value) {
75
+ const num = parseFloat(value);
76
+ return isNaN(num) ? 0 : num;
77
+ }
78
+
79
+ /**
80
+ * Boolean sanitization
81
+ */
82
+ export function sanitizeBoolean(value) {
83
+ if (typeof value === "boolean") return value;
84
+ if (typeof value === "string") {
85
+ return value.toLowerCase() === "true" || value === "1";
86
+ }
87
+ return Boolean(value);
88
+ }
89
+
90
+ /**
91
+ * Date sanitization — return ISO string
92
+ */
93
+ export function sanitizeDate(value) {
94
+ if (value instanceof Date) return value.toISOString().split("T")[0];
95
+ if (typeof value === "string") {
96
+ const d = new Date(value);
97
+ return isNaN(d.getTime()) ? null : d.toISOString().split("T")[0];
98
+ }
99
+ return null;
100
+ }
101
+
102
+ /**
103
+ * Array sanitization — remove empty, null, duplicate entries
104
+ */
105
+ export function sanitizeArray(arr) {
106
+ if (!Array.isArray(arr)) return [];
107
+ return [...new Set(arr.filter(v => v != null && v !== "" && typeof v === "string" ? v.trim() : v))];
108
+ }
109
+
110
+ /**
111
+ * Full form data sanitization based on field config
112
+ */
113
+ export function sanitizeFormData(data, fieldConfigs) {
114
+ const sanitized = {};
115
+
116
+ for (const field of fieldConfigs) {
117
+ const value = data[field.name];
118
+
119
+ if (value === undefined || value === null) {
120
+ sanitized[field.name] = field.default || getDefaultValue(field.type);
121
+ continue;
122
+ }
123
+
124
+ const sanitizer = SANITIZER_MAP[field.type] || ((v) => v);
125
+ sanitized[field.name] = sanitizer(value);
126
+ }
127
+
128
+ return sanitized;
129
+ }
130
+
131
+ function getDefaultValue(type) {
132
+ const defaults = {
133
+ string: "",
134
+ text: "",
135
+ email: "",
136
+ number: 0,
137
+ boolean: false,
138
+ date: null,
139
+ array: [],
140
+ };
141
+ return defaults[type] ?? null;
142
+ }
143
+
144
+ const SANITIZER_MAP = {
145
+ text: sanitizeText,
146
+ textarea: sanitizeText,
147
+ email: sanitizeEmail,
148
+ password: (v) => v, // Don't sanitize passwords
149
+ number: sanitizeNumber,
150
+ tel: sanitizePhone,
151
+ url: sanitizeUrl,
152
+ date: sanitizeDate,
153
+ "datetime-local": sanitizeDate,
154
+ time: (v) => String(v),
155
+ color: (v) => String(v).toUpperCase(),
156
+ range: sanitizeNumber,
157
+ boolean: sanitizeBoolean,
158
+ file: (v) => v,
159
+ };
160
+
161
+ /**
162
+ * Escape HTML entities for safe rendering
163
+ */
164
+ export function escapeHtml(value) {
165
+ if (typeof value !== "string") return value;
166
+ return value
167
+ .replace(/&/g, "&amp;")
168
+ .replace(/</g, "&lt;")
169
+ .replace(/>/g, "&gt;")
170
+ .replace(/"/g, "&quot;")
171
+ .replace(/'/g, "&#039;");
172
+ }
173
+
174
+ /**
175
+ * Validate phone number format
176
+ */
177
+ export function isValidPhone(value) {
178
+ const cleaned = sanitizePhone(value);
179
+ return /^[+]?[1-9]\d{1,14}$/.test(cleaned);
180
+ }
181
+
182
+ /**
183
+ * Validate email format
184
+ */
185
+ export function isValidEmail(value) {
186
+ if (typeof value !== "string") return false;
187
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
188
+ }
189
+
190
+ /**
191
+ * Validate URL format
192
+ */
193
+ export function isValidUrl(value) {
194
+ if (typeof value !== "string") return false;
195
+ return /^https?:\/\/.+\..+/.test(value);
196
+ }
197
+
198
+ // Export sanitizer map for code generation
199
+ export { SANITIZER_MAP };
200
+