@startinblox/boilerplate 5.0.0 → 6.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 (52) hide show
  1. package/.gitlab-ci.yml +1 -1
  2. package/AGENTS.md +518 -0
  3. package/README.md +16 -19
  4. package/biome.json +1 -1
  5. package/cypress/component/sample-object.cy.ts +816 -0
  6. package/cypress/component/solid-boilerplate.cy.ts +894 -5
  7. package/cypress/e2e/helpers/components/setupCacheInvalidation.cy.ts +512 -0
  8. package/cypress/e2e/helpers/components/setupCacheOnResourceReady.cy.ts +497 -0
  9. package/cypress/e2e/helpers/components/setupComponentSubscriptions.cy.ts +239 -0
  10. package/cypress/e2e/helpers/components/setupOnSaveReset.cy.ts +393 -0
  11. package/cypress/e2e/helpers/datas/checkValueInIntervalRecursive.cy.ts +563 -0
  12. package/cypress/e2e/helpers/datas/dataBuilder.cy.ts +508 -0
  13. package/cypress/e2e/helpers/datas/filterGenerator.cy.ts +285 -0
  14. package/cypress/e2e/helpers/datas/filterObjectByDateAfter.cy.ts +389 -0
  15. package/cypress/e2e/helpers/datas/filterObjectByDateInterval.cy.ts +613 -0
  16. package/cypress/e2e/helpers/datas/filterObjectById.cy.ts +276 -0
  17. package/cypress/e2e/helpers/datas/filterObjectByInterval.cy.ts +237 -0
  18. package/cypress/e2e/helpers/datas/filterObjectByNamedValue.cy.ts +299 -0
  19. package/cypress/e2e/helpers/datas/filterObjectByType.cy.ts +307 -0
  20. package/cypress/e2e/helpers/datas/filterObjectByValue.cy.ts +375 -0
  21. package/cypress/e2e/helpers/datas/sort.cy.ts +293 -0
  22. package/cypress/e2e/helpers/ui/formatDate.cy.ts +233 -0
  23. package/cypress/e2e/helpers/utils/requestNavigation.cy.ts +257 -0
  24. package/cypress/e2e/helpers/utils/uniq.cy.ts +160 -0
  25. package/cypress/support/e2e.ts +1 -0
  26. package/cypress.config.ts +2 -0
  27. package/dist/index.js +585 -550
  28. package/package.json +11 -9
  29. package/scripts/init.js +355 -0
  30. package/src/components/solid-boilerplate.ts +3 -6
  31. package/src/helpers/components/componentObjectHandler.ts +5 -7
  32. package/src/helpers/components/componentObjectsHandler.ts +8 -3
  33. package/src/helpers/components/orbitComponent.ts +87 -68
  34. package/src/helpers/components/setupCacheInvalidation.ts +50 -23
  35. package/src/helpers/components/setupCacheOnResourceReady.ts +42 -23
  36. package/src/helpers/components/setupComponentSubscriptions.ts +10 -9
  37. package/src/helpers/components/setupOnSaveReset.ts +27 -5
  38. package/src/helpers/datas/checkValueInIntervalRecursive.ts +66 -0
  39. package/src/helpers/datas/dataBuilder.ts +4 -4
  40. package/src/helpers/datas/filterGenerator.ts +13 -10
  41. package/src/helpers/datas/filterObjectByDateAfter.ts +3 -3
  42. package/src/helpers/datas/filterObjectByDateInterval.ts +44 -0
  43. package/src/helpers/datas/filterObjectById.ts +7 -6
  44. package/src/helpers/datas/filterObjectByInterval.ts +6 -110
  45. package/src/helpers/datas/filterObjectByNamedValue.ts +35 -33
  46. package/src/helpers/datas/filterObjectByType.ts +3 -3
  47. package/src/helpers/datas/filterObjectByValue.ts +17 -16
  48. package/src/helpers/datas/sort.ts +50 -23
  49. package/src/helpers/index.ts +6 -4
  50. package/src/helpers/ui/formatDate.ts +14 -1
  51. package/src/helpers/utils/requestNavigation.ts +5 -2
  52. package/src/helpers/utils/uniq.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startinblox/boilerplate",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "Startin'blox Boilerplate",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -52,11 +52,12 @@
52
52
  "access": "public"
53
53
  },
54
54
  "scripts": {
55
+ "init": "node scripts/init.js",
55
56
  "watch": "lit-localize build && vite",
56
57
  "build": "lit-localize build && vite build",
57
58
  "serve": "vite preview",
58
- "cy:open": "cypress open --component",
59
- "cy:run": "lit-localize build && cypress run --component",
59
+ "cy:open": "cypress open",
60
+ "cy:run": "lit-localize build && cypress run --e2e && cypress run --component",
60
61
  "storybook": "lit-localize build && storybook dev -p 6006",
61
62
  "build-storybook": "lit-localize build && storybook build",
62
63
  "locale:extract": "lit-localize extract",
@@ -66,13 +67,14 @@
66
67
  "last 2 Chrome versions"
67
68
  ],
68
69
  "dependencies": {
70
+ "@inquirer/prompts": "^8.2.0",
69
71
  "@lit/localize": "^0.12.2",
70
72
  "@lit/task": "^1.0.3",
71
- "autoprefixer": "^10.4.23",
73
+ "autoprefixer": "^10.4.24",
72
74
  "cssnano": "^7.1.2",
73
75
  "lit": "^3.3.2",
74
76
  "postcss": "^8.5.6",
75
- "postcss-preset-env": "^11.1.2",
77
+ "postcss-preset-env": "^11.1.3",
76
78
  "postcss-scss": "^4.0.9",
77
79
  "sass": "^1.97.3",
78
80
  "unplugin-icons": "^23.0.1",
@@ -80,11 +82,11 @@
80
82
  },
81
83
  "devDependencies": {
82
84
  "@lit/localize-tools": "^0.8.1",
83
- "@storybook/addon-docs": "^10.2.3",
84
- "@storybook/web-components-vite": "^10.2.3",
85
- "cypress": "^15.9.0",
85
+ "@storybook/addon-docs": "^10.2.8",
86
+ "@storybook/web-components-vite": "^10.2.8",
87
+ "cypress": "^15.10.0",
86
88
  "cypress-ct-lit": "^1.0.0",
87
89
  "lorem-ipsum": "^2.0.8",
88
- "storybook": "^10.2.3"
90
+ "storybook": "^10.2.8"
89
91
  }
90
92
  }
@@ -0,0 +1,355 @@
1
+ import { execSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { confirm, input } from "@inquirer/prompts";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const rootDir = path.resolve(__dirname, "..");
10
+
11
+ const EXPECTED_NODE_MAJOR = 24;
12
+
13
+ async function checkNodeVersion() {
14
+ const currentVersion = process.version;
15
+ const majorVersion = Number.parseInt(
16
+ currentVersion.slice(1).split(".")[0],
17
+ 10,
18
+ );
19
+
20
+ if (majorVersion !== EXPECTED_NODE_MAJOR) {
21
+ console.error("\x1b[31m%s\x1b[0m", "\n✗ Node version mismatch!");
22
+ console.error("\x1b[31m%s\x1b[0m", ` Expected: ${EXPECTED_NODE_MAJOR}`);
23
+ console.error("\x1b[31m%s\x1b[0m", ` Current: ${majorVersion}`);
24
+ console.error(
25
+ "\x1b[31m%s\x1b[0m",
26
+ `\n Please use Node ${EXPECTED_NODE_MAJOR}.`,
27
+ );
28
+
29
+ const shouldContinue = await confirm({
30
+ message: "Do you want to continue anyway?",
31
+ default: false,
32
+ });
33
+
34
+ if (!shouldContinue) {
35
+ console.log("\n\x1b[33m%s\x1b[0m", "Initialization cancelled.");
36
+ process.exit(1);
37
+ }
38
+
39
+ console.log(
40
+ "\n\x1b[33m%s\x1b[0m",
41
+ "⚠ Continuing with unsupported Node version...",
42
+ );
43
+ }
44
+ }
45
+
46
+ async function main() {
47
+ console.log(
48
+ "\n\x1b[36m%s\x1b[0m",
49
+ "╔══════════════════════════════════════════╗",
50
+ );
51
+ console.log(
52
+ "\x1b[36m%s\x1b[0m",
53
+ "║ Startin'blox Component Boilerplate ║",
54
+ );
55
+ console.log(
56
+ "\x1b[36m%s\x1b[0m",
57
+ "║ Initialization Wizard ║",
58
+ );
59
+ console.log(
60
+ "\x1b[36m%s\x1b[0m",
61
+ "╚══════════════════════════════════════════╝\n",
62
+ );
63
+
64
+ // Check Node.js version before proceeding
65
+ await checkNodeVersion();
66
+
67
+ const packageName = await input({
68
+ message: "Enter package name (must start with @startinblox/):",
69
+ default: "@startinblox/my-component",
70
+ validate: (value) => {
71
+ const regex = /^@startinblox\/[a-z0-9-]+$/;
72
+ if (!regex.test(value)) {
73
+ return "Must be in format @startinblox/name (lowercase, alphanumeric, hyphens only)";
74
+ }
75
+ return true;
76
+ },
77
+ });
78
+
79
+ const description = await input({
80
+ message: "Enter project description (optional, press Enter to skip):",
81
+ });
82
+
83
+ const author = await input({
84
+ message: "Enter author name (optional, press Enter to skip):",
85
+ });
86
+
87
+ const repository = await input({
88
+ message: "Enter repository URL (optional, press Enter to skip):",
89
+ validate: (value) => {
90
+ if (!value) return true;
91
+ try {
92
+ new URL(value);
93
+ return true;
94
+ } catch {
95
+ return "Must be a valid URL";
96
+ }
97
+ },
98
+ });
99
+
100
+ const useDefaultLicense = await confirm({
101
+ message: "Use MIT license?",
102
+ default: true,
103
+ });
104
+
105
+ const license = useDefaultLicense
106
+ ? "MIT"
107
+ : await input({
108
+ message: "Enter license:",
109
+ default: "MIT",
110
+ });
111
+
112
+ const nameWithoutScope = packageName.replace("@startinblox/", "");
113
+ const componentName = `solid-${nameWithoutScope}`;
114
+ const className = `Solid${toPascalCase(nameWithoutScope).replace(/^Solid/, "")}`;
115
+ const camelCaseName = toCamelCase(nameWithoutScope);
116
+
117
+ updatePackageJson({ packageName, description, author, repository, license });
118
+ generateComponentFile({
119
+ nameWithoutScope,
120
+ componentName,
121
+ className,
122
+ camelCaseName,
123
+ });
124
+ removeSampleFiles();
125
+
126
+ const removeE2ETests = await confirm({
127
+ message: "Do you want to remove the boilerplate cypress e2e tests? (keep them for easier future updates)",
128
+ default: true,
129
+ });
130
+
131
+ if (removeE2ETests) {
132
+ removeE2ETestsFiles();
133
+ }
134
+
135
+ replaceReadme({ packageName, description });
136
+ handleGitRemotes();
137
+
138
+ setTimeout(() => {
139
+ try {
140
+ fs.rmSync(path.join(__dirname), { recursive: true, force: true });
141
+ console.log("\n\x1b[32m%s\x1b[0m", "✓ Init script self-cleanup complete");
142
+ } catch (error) {
143
+ console.warn(
144
+ "\n\x1b[33m%s\x1b[0m",
145
+ "⚠ Warning: Could not delete scripts folder",
146
+ );
147
+ if (process.env.NODE_ENV === "development") {
148
+ console.warn(error);
149
+ }
150
+ }
151
+
152
+ console.log("\n\x1b[32m%s\x1b[0m", "✓ Initialization complete!\n");
153
+ console.log("\x1b[36m%s\x1b[0m", "Next steps:");
154
+ console.log(" 1. Review the changes above");
155
+ console.log(" 2. ⚠ Configure Git remotes if instructed ⚠");
156
+ console.log(" 3. Run `npm install`");
157
+ console.log(" 4. Make your initial commit:");
158
+ console.log(
159
+ "\x1b[33m%s\x1b[0m",
160
+ " git add . && git commit -m 'major: Initial commit'\n",
161
+ );
162
+ }, 100);
163
+ }
164
+
165
+ function toPascalCase(str) {
166
+ return str
167
+ .split("-")
168
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
169
+ .join("");
170
+ }
171
+
172
+ function toCamelCase(str) {
173
+ const pascal = str
174
+ .split("-")
175
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
176
+ .join("");
177
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
178
+ }
179
+
180
+ function updatePackageJson({
181
+ packageName,
182
+ description,
183
+ author,
184
+ repository,
185
+ license,
186
+ }) {
187
+ try {
188
+ const packageJsonPath = path.join(rootDir, "package.json");
189
+ const packageJson = JSON.parse(
190
+ fs.readFileSync(packageJsonPath, { encoding: "utf-8" }),
191
+ );
192
+
193
+ packageJson.name = packageName;
194
+ if (description) packageJson.description = description;
195
+ if (author) packageJson.author = author;
196
+ if (repository) packageJson.repository.url = repository;
197
+ packageJson.license = license;
198
+
199
+ delete packageJson.scripts.init;
200
+ if (packageJson.dependencies?.["@inquirer/prompts"]) {
201
+ delete packageJson.dependencies["@inquirer/prompts"];
202
+ }
203
+
204
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), {
205
+ encoding: "utf-8",
206
+ });
207
+ console.log("\n\x1b[32m%s\x1b[0m", "✓ package.json updated");
208
+ } catch (error) {
209
+ console.error("\x1b[31m%s\x1b[0m", "✗ Failed to update package.json");
210
+ console.error(error.message);
211
+ process.exit(1);
212
+ }
213
+ }
214
+
215
+ function generateComponentFile({
216
+ nameWithoutScope,
217
+ componentName,
218
+ className,
219
+ camelCaseName,
220
+ }) {
221
+ try {
222
+ const templatePath = path.join(
223
+ rootDir,
224
+ "src/components/solid-boilerplate.ts",
225
+ );
226
+ const template = fs.readFileSync(templatePath, { encoding: "utf-8" });
227
+
228
+ const content = template
229
+ .replace(/solid-boilerplate/g, componentName)
230
+ .replace(/SolidBoilerplate/g, className)
231
+ .replace(/solidBoilerplate/g, camelCaseName)
232
+ .replace(/boilerplate/g, nameWithoutScope);
233
+
234
+ const outputPath = path.join(rootDir, `src/components/${componentName}.ts`);
235
+ fs.writeFileSync(outputPath, content, { encoding: "utf-8" });
236
+ console.log(
237
+ "\x1b[32m%s\x1b[0m",
238
+ `✓ Created src/components/${componentName}.ts`,
239
+ );
240
+ } catch (error) {
241
+ console.error("\x1b[31m%s\x1b[0m", "✗ Failed to generate component file");
242
+ console.error(error.message);
243
+ process.exit(1);
244
+ }
245
+ }
246
+
247
+ function removeSampleFiles() {
248
+ const filesToRemove = [
249
+ "src/components/solid-boilerplate.ts",
250
+ "src/components/ui/sample-object.ts",
251
+ "src/components/ui/sample-objects.ts",
252
+ "cypress/component/sample-object.cy.ts",
253
+ "cypress/component/solid-boilerplate.cy.ts",
254
+ "stories/sample-objects.stories.ts",
255
+ "src/styles/component-sample.scss",
256
+ ];
257
+
258
+ filesToRemove.forEach((filePath) => {
259
+ try {
260
+ const fullPath = path.join(rootDir, filePath);
261
+ if (fs.existsSync(fullPath)) {
262
+ fs.rmSync(fullPath, { recursive: true, force: true });
263
+ console.log("\x1b[32m%s\x1b[0m", `✓ Removed ${filePath}`);
264
+ }
265
+ } catch (_error) {
266
+ console.warn("\x1b[33m%s\x1b[0m", `⚠ Could not remove ${filePath}`);
267
+ }
268
+ });
269
+ }
270
+
271
+ function removeE2ETestsFiles() {
272
+ const filesToRemove = ["cypress/e2e/helpers"];
273
+
274
+ filesToRemove.forEach((filePath) => {
275
+ try {
276
+ const fullPath = path.join(rootDir, filePath);
277
+ if (fs.existsSync(fullPath)) {
278
+ fs.rmSync(fullPath, { recursive: true, force: true });
279
+ console.log("\x1b[32m%s\x1b[0m", `✓ Removed ${filePath}`);
280
+ }
281
+ } catch (_error) {
282
+ console.warn("\x1b[33m%s\x1b[0m", `⚠ Could not remove ${filePath}`);
283
+ }
284
+ });
285
+ }
286
+
287
+ function replaceReadme({ packageName, description }) {
288
+ try {
289
+ const readmeContent = `# ${packageName}
290
+
291
+ ${description || "A Startin'blox component."}
292
+
293
+ ## Usage
294
+
295
+ \`\`\`html
296
+ <${packageName.replace("@startinblox/", "solid-")}></${packageName.replace("@startinblox/", "solid-")}>
297
+ \`\`\`
298
+
299
+ ## Development
300
+
301
+ \`\`\`bash
302
+ npm run watch
303
+ \`\`\`
304
+
305
+ ## Build
306
+
307
+ \`\`\`bash
308
+ npm run build
309
+ \`\`\`
310
+ `;
311
+
312
+ const readmePath = path.join(rootDir, "README.md");
313
+ fs.writeFileSync(readmePath, readmeContent, { encoding: "utf-8" });
314
+ console.log("\x1b[32m%s\x1b[0m", "✓ Created new README.md");
315
+ } catch (error) {
316
+ console.error("\x1b[31m%s\x1b[0m", "✗ Failed to create README.md");
317
+ console.error(error.message);
318
+ process.exit(1);
319
+ }
320
+ }
321
+
322
+ function handleGitRemotes() {
323
+ try {
324
+ const gitOutput = execSync("git remote -v", { encoding: "utf-8" });
325
+ const hasBoilerplateOrigin = gitOutput.includes(
326
+ "components/solid-boilerplate.git",
327
+ );
328
+
329
+ if (hasBoilerplateOrigin) {
330
+ console.log("\n\x1b[36m%s\x1b[0m", "Git Configuration:");
331
+ console.log(" Your origin remote points to the boilerplate repository.");
332
+ console.log(" Run these commands to set up your own repository:\n");
333
+ console.log("\x1b[33m%s\x1b[0m", " git remote rename origin upstream");
334
+ console.log(" git remote add origin <your-repo-url>\n");
335
+ } else {
336
+ console.log("\n\x1b[36m%s\x1b[0m", "Git Configuration:");
337
+ console.log(" To track boilerplate updates, run:\n");
338
+ console.log(
339
+ "\x1b[33m%s\x1b[0m",
340
+ " git remote add upstream https://git.startinblox.com/components/solid-boilerplate.git\n",
341
+ );
342
+ }
343
+ } catch (_error) {
344
+ console.warn(
345
+ "\n\x1b[33m%s\x1b[0m",
346
+ "⚠ Could not detect Git remotes (may not be a git repository)",
347
+ );
348
+ }
349
+ }
350
+
351
+ main().catch((error) => {
352
+ console.error("\x1b[31m%s\x1b[0m", "\n✗ Initialization failed");
353
+ console.error(error.message);
354
+ process.exit(1);
355
+ });
@@ -1,8 +1,7 @@
1
- import { css, html, nothing } from "lit";
2
- import { customElement } from "lit/decorators.js";
3
1
  import { Task } from "@lit/task";
4
-
5
2
  import type { PropertiesPicker } from "@src/component";
3
+ import { css, html, nothing } from "lit";
4
+ import { customElement } from "lit/decorators.js";
6
5
 
7
6
  import "@src/initializer";
8
7
  import * as utils from "@helpers";
@@ -28,9 +27,7 @@ export class SolidBoilerplate extends utils.OrbitComponent {
28
27
  }
29
28
  `;
30
29
 
31
- cherryPickedProperties: PropertiesPicker[] = [
32
- { key: "name", value: "name" },
33
- ];
30
+ cherryPickedProperties: PropertiesPicker[] = [{ key: "name", value: "name" }];
34
31
 
35
32
  _getResource = new Task(this, {
36
33
  task: async ([dataSrc]) => {
@@ -1,16 +1,14 @@
1
- import type {
2
- LimitedResource,
3
- Resource,
4
- } from "@src/component";
1
+ import type { LimitedResource, Resource } from "@src/component";
5
2
  import { LitElement } from "lit";
6
3
 
7
4
  export class ComponentObjectHandler extends LitElement {
8
5
  object?: LimitedResource = {
9
- "@id": ""
6
+ "@id": "",
10
7
  };
11
8
 
12
- isType = (type: string, obj: Resource = (this.object as Resource)) => {
13
- const typeValue = obj["@type"];
9
+ isType = (type: string, obj?: Resource) => {
10
+ const resource = obj ?? (this.object as Resource);
11
+ const typeValue = resource["@type"];
14
12
  if (Array.isArray(typeValue)) {
15
13
  return typeValue.includes(type);
16
14
  }
@@ -7,8 +7,13 @@ export class ComponentObjectsHandler extends LitElement {
7
7
  @property({ attribute: false })
8
8
  objects?: Resource[] = [];
9
9
 
10
- hasType = (type: string, objs: Resource[] = this.objects as Resource[]) => {
11
- const typeValue = new Set(objs.flatMap((o) => o["@type"]));
12
- return typeValue.has(type);
10
+ hasType = (type: string, objs?: Resource[]) => {
11
+ const resources = objs ?? this.objects ?? [];
12
+ return resources.some((o) => {
13
+ const typeValue = o["@type"];
14
+ if (Array.isArray(typeValue)) return typeValue.includes(type);
15
+ if (typeof typeValue === "string") return typeValue === type;
16
+ return false;
17
+ });
13
18
  };
14
19
  }