@newlogic-digital/cli 1.5.0-next.1 → 1.5.0-next.3
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/index.mjs +69 -0
- package/package.json +2 -1
- package/skills/newlogic/SKILL.md +89 -0
- package/skills/newlogic/agents/openai.yaml +4 -0
- package/src/templates/components/article/ArticleGridHorizontal.latte +28 -0
- package/src/templates/components/article/ArticleItem.latte +59 -0
- package/src/templates/components/header/HeaderNavLeft.latte +71 -0
package/index.mjs
CHANGED
|
@@ -6,10 +6,72 @@ import blocks from './src/commands/blocks/index.mjs'
|
|
|
6
6
|
import { styleText } from 'node:util'
|
|
7
7
|
import { version, name } from './src/utils.mjs'
|
|
8
8
|
|
|
9
|
+
const knownCommands = ['init', 'cms', 'blocks']
|
|
10
|
+
|
|
9
11
|
function normalizeOptionName(name) {
|
|
10
12
|
return name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
function getLevenshteinDistance(left, right) {
|
|
16
|
+
const distances = Array.from({ length: left.length + 1 }, () => Array(right.length + 1).fill(0))
|
|
17
|
+
|
|
18
|
+
for (let row = 0; row <= left.length; row++) {
|
|
19
|
+
distances[row][0] = row
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (let column = 0; column <= right.length; column++) {
|
|
23
|
+
distances[0][column] = column
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (let row = 1; row <= left.length; row++) {
|
|
27
|
+
for (let column = 1; column <= right.length; column++) {
|
|
28
|
+
const substitutionCost = left[row - 1] === right[column - 1] ? 0 : 1
|
|
29
|
+
|
|
30
|
+
distances[row][column] = Math.min(
|
|
31
|
+
distances[row - 1][column] + 1,
|
|
32
|
+
distances[row][column - 1] + 1,
|
|
33
|
+
distances[row - 1][column - 1] + substitutionCost,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return distances[left.length][right.length]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getSuggestedCommand(command) {
|
|
42
|
+
const normalizedCommand = `${command ?? ''}`.trim().toLowerCase()
|
|
43
|
+
|
|
44
|
+
if (!normalizedCommand) {
|
|
45
|
+
return undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const suggestion = knownCommands
|
|
49
|
+
.map(candidate => ({ candidate, distance: getLevenshteinDistance(normalizedCommand, candidate) }))
|
|
50
|
+
.sort((left, right) => left.distance - right.distance)[0]
|
|
51
|
+
|
|
52
|
+
if (suggestion && suggestion.distance <= 2) {
|
|
53
|
+
return suggestion.candidate
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function printUnknownCommand(command) {
|
|
60
|
+
const suggestion = getSuggestedCommand(command)
|
|
61
|
+
const lines = [
|
|
62
|
+
`${styleText(['red', 'bold'], 'error')} Unknown command ${styleText(['yellow', 'bold'], `"${command}"`)}`,
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
if (suggestion) {
|
|
66
|
+
lines.push(`${styleText(['cyan', 'bold'], 'hint')} Did you mean ${styleText(['green', 'bold'], `"${suggestion}"`)}?`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
lines.push(`${styleText(['white', 'bold'], 'available')} ${knownCommands.map(command => styleText('green', command)).join(', ')}`)
|
|
70
|
+
lines.push(`${styleText(['cyan', 'bold'], 'help')} Run ${styleText('green', 'newlogic')} to see full usage`)
|
|
71
|
+
|
|
72
|
+
console.log(lines.join('\n'))
|
|
73
|
+
}
|
|
74
|
+
|
|
13
75
|
function parseCommandArgs(args) {
|
|
14
76
|
const positionals = []
|
|
15
77
|
const options = {}
|
|
@@ -97,6 +159,8 @@ if (!command) {
|
|
|
97
159
|
${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', 'header-nav-left hero-floating-text')}
|
|
98
160
|
${styleText('green', 'newlogic blocks update')}
|
|
99
161
|
`)
|
|
162
|
+
|
|
163
|
+
process.exit(0)
|
|
100
164
|
}
|
|
101
165
|
|
|
102
166
|
if (command === 'init') {
|
|
@@ -122,3 +186,8 @@ if (command === 'blocks') {
|
|
|
122
186
|
|
|
123
187
|
await blocks(action, names)
|
|
124
188
|
}
|
|
189
|
+
|
|
190
|
+
if (command && !knownCommands.includes(command)) {
|
|
191
|
+
printUnknownCommand(command)
|
|
192
|
+
process.exit(1)
|
|
193
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newlogic-digital/cli",
|
|
3
|
-
"version": "1.5.0-next.
|
|
3
|
+
"version": "1.5.0-next.3",
|
|
4
4
|
"main": "index.mjs",
|
|
5
5
|
"bin": {
|
|
6
6
|
"newlogic-cli": "index.mjs",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"index.js",
|
|
25
|
+
"skills",
|
|
25
26
|
"src"
|
|
26
27
|
],
|
|
27
28
|
"engines": {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: newlogic
|
|
3
|
+
description: Use the installed Newlogic CLI when Codex needs to run `newlogic`, scaffold `@newlogic-digital/ui` or `@newlogic-digital/cms` projects, prepare CMS templates and components, or manage installable blocks through `newlogic.config.json`. Trigger this skill for tasks involving the commands `init`, `cms`, or `blocks`, especially when an agent should run the CLI safely and non-interactively.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Newlogic CLI
|
|
7
|
+
|
|
8
|
+
## Quick Rules
|
|
9
|
+
|
|
10
|
+
- Run commands from the target project directory. `cms` and `blocks` operate on the current working tree.
|
|
11
|
+
- Prefer explicit flags to prompts. For automated runs, pass `-y` and any necessary options.
|
|
12
|
+
- Do not probe for `newlogic` in advance. Install it only after a real command fails because the binary is missing.
|
|
13
|
+
- Expect Node `>=22` and npm `>=10`.
|
|
14
|
+
- Use `newlogic` in user-facing examples.
|
|
15
|
+
|
|
16
|
+
## Commands
|
|
17
|
+
|
|
18
|
+
### `init`
|
|
19
|
+
|
|
20
|
+
Use to scaffold a new project.
|
|
21
|
+
|
|
22
|
+
- `newlogic init`
|
|
23
|
+
- `newlogic init ui <directory>`
|
|
24
|
+
- `newlogic init cms <directory>`
|
|
25
|
+
|
|
26
|
+
Useful non-interactive options:
|
|
27
|
+
|
|
28
|
+
- `--branch=main|dev`
|
|
29
|
+
- `--clone=ssh|https`
|
|
30
|
+
- `--scope=default|blank` for `ui`
|
|
31
|
+
- `--variant=cms-web|cms-eshop` for `cms`
|
|
32
|
+
- `--git`, `--remote=<git-url>`, `--install`
|
|
33
|
+
- `--prepare`, `--dev`, `--migrations` for `cms`
|
|
34
|
+
- `-y` to accept defaults without prompts
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
newlogic init ui demo --branch=dev --clone=https --scope=blank --git --remote=git@github.com:org/demo.git --install -y
|
|
40
|
+
newlogic init cms demo --branch=dev --clone=https --variant=cms-web --install --prepare --dev --migrations -y
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### `cms`
|
|
44
|
+
|
|
45
|
+
Use inside a CMS project.
|
|
46
|
+
|
|
47
|
+
- `newlogic cms prepare`
|
|
48
|
+
- `newlogic cms prepare views`
|
|
49
|
+
- `newlogic cms prepare components`
|
|
50
|
+
- `newlogic cms new-component <name>`
|
|
51
|
+
|
|
52
|
+
Notes:
|
|
53
|
+
|
|
54
|
+
- `prepare` copies templates and components from a UI project into CMS structure.
|
|
55
|
+
- `new-component` creates PHP component scaffolding and clears `temp/cache` if present.
|
|
56
|
+
- Deprecated aliases such as `new-section` still exist in code, but prefer `new-component`.
|
|
57
|
+
|
|
58
|
+
### `blocks`
|
|
59
|
+
|
|
60
|
+
Use to manage installable blocks in a project root.
|
|
61
|
+
|
|
62
|
+
- `newlogic blocks list`
|
|
63
|
+
- `newlogic blocks add <name...>`
|
|
64
|
+
- `newlogic blocks remove <name...>`
|
|
65
|
+
- `newlogic blocks update`
|
|
66
|
+
|
|
67
|
+
Notes:
|
|
68
|
+
|
|
69
|
+
- `list` prints all installable blocks with short descriptions so the agent can discover valid names before making changes.
|
|
70
|
+
- `add` accepts kebab-case or PascalCase block names.
|
|
71
|
+
- Installed blocks are recorded in `newlogic.config.json`.
|
|
72
|
+
- `update` reinstalls all configured blocks from `newlogic.config.json`.
|
|
73
|
+
- `add` and `remove` may modify files and install npm or composer dependencies.
|
|
74
|
+
|
|
75
|
+
Recommended flow:
|
|
76
|
+
|
|
77
|
+
1. Run `newlogic blocks list` to discover what blocks exist.
|
|
78
|
+
2. Match the user request to one or more listed block names.
|
|
79
|
+
3. Run `newlogic blocks add <name...>` only after the names are confirmed by the list output.
|
|
80
|
+
4. Use `remove` only when the user wants to delete blocks that are already installed in the current project.
|
|
81
|
+
5. Use `update` when the goal is to reinstall everything already tracked in `newlogic.config.json`.
|
|
82
|
+
|
|
83
|
+
## Agent Workflow
|
|
84
|
+
|
|
85
|
+
1. Choose the command based on intent: scaffold with `init`, sync CMS artifacts with `cms`, manage reusable blocks with `blocks`.
|
|
86
|
+
2. Run it from the correct working directory.
|
|
87
|
+
3. If the command fails because `newlogic` is missing, install it with `npm i -g @newlogic-digital/cli` and retry.
|
|
88
|
+
4. Prefer explicit flags and `-y` for deterministic execution.
|
|
89
|
+
5. After mutation commands, inspect changed files and report side effects such as generated folders, dependency installs, or `newlogic.config.json` updates.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<section class="x-article-grid-horizontal grid grid-cols-container gap-y-12 md:gap-y-16 py-20 md:py-24 lg:py-28">
|
|
2
|
+
<div class="flex items-end gap-x-10 gap-y-6 w-full justify-between max-md:flex-col">
|
|
3
|
+
<div class="flex flex-col w-full lg:flex-1/2 items-start max-w-161">
|
|
4
|
+
<div class="x-badge mb-1 accent-main-secondary muted">
|
|
5
|
+
Section title
|
|
6
|
+
</div>
|
|
7
|
+
<h2 class="x-heading lg:lg mb-6">
|
|
8
|
+
Attention-grabbing medium length section headline
|
|
9
|
+
</h2>
|
|
10
|
+
<div class="x-text w-full">
|
|
11
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
|
12
|
+
Suspendisse varius enim in eros elementum tristique.
|
|
13
|
+
Duis cursus, mi quis viverra ornare, eros dolor interdum nulla.
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<a href="#" class="x-button lg max-md:mr-auto">
|
|
17
|
+
View all
|
|
18
|
+
<svg class="me-auto size-5" aria-hidden="true">
|
|
19
|
+
<use href="#heroicons-mini/arrow-right"/>
|
|
20
|
+
</svg>
|
|
21
|
+
</a>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
24
|
+
{foreach range(1,$control->itemsCount ?? 6) as $i}
|
|
25
|
+
{include './ArticleItem.latte', itemVariant => $control->itemVariant, showAuthor => $control->showAuthor, lastHidden => true, mutedBadge => $control->itemVariant !== 'filled'}
|
|
26
|
+
{/foreach}
|
|
27
|
+
</div>
|
|
28
|
+
</section>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{default $lastHidden = false}
|
|
2
|
+
{default $itemVariant = 'basic'}
|
|
3
|
+
{default $mutedBadge = false}
|
|
4
|
+
{default $showAuthor = false}
|
|
5
|
+
|
|
6
|
+
<a
|
|
7
|
+
n:class="'x-article-item group flex flex-col', $lastHidden ? 'max-md:last:hidden lg:last:hidden'"
|
|
8
|
+
href="/article/detail.html"
|
|
9
|
+
>
|
|
10
|
+
<picture
|
|
11
|
+
n:class="
|
|
12
|
+
'x-image w-full before:skeleton aspect-310/207 md:aspect-310/207 lg:aspect-418/279 overflow-hidden',
|
|
13
|
+
$itemVariant === 'basic' ? 'rounded-2xl',
|
|
14
|
+
$itemVariant === 'outlined' ? 'rounded-t-2xl border-x border-t border-body-secondary',
|
|
15
|
+
$itemVariant === 'filled' ? 'rounded-t-2xl',
|
|
16
|
+
"
|
|
17
|
+
>
|
|
18
|
+
<source srcset="{=placeholder(345,431)}" media="(width < {$media->sm})">
|
|
19
|
+
<img
|
|
20
|
+
class="size-full object-cover group-hocus:scale-105 transition-transform"
|
|
21
|
+
src="{=placeholder(532,655)}"
|
|
22
|
+
alt=" " width="532" height="655" loading="lazy"
|
|
23
|
+
>
|
|
24
|
+
</picture>
|
|
25
|
+
<div
|
|
26
|
+
n:class="
|
|
27
|
+
'flex flex-col items-start pt-6 grow',
|
|
28
|
+
$itemVariant === 'outlined' ? 'rounded-b-2xl border-x border-b border-body-secondary px-6 pb-4',
|
|
29
|
+
$itemVariant === 'filled' ? 'bg-primary/5 px-6 pb-4 rounded-b-2xl',
|
|
30
|
+
"
|
|
31
|
+
>
|
|
32
|
+
<div class="flex gap-2 flex-wrap mb-3">
|
|
33
|
+
<div n:class="'x-badge sm', $mutedBadge ? 'muted' : ''">Article category</div>
|
|
34
|
+
<div class="x-badge sm muted bg-transparent" n:if="!$showAuthor">5 min read</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="x-heading xs mb-4 tracking-[-0.5px]">
|
|
37
|
+
Article title goes here
|
|
38
|
+
</div>
|
|
39
|
+
<div class="x-text text-main-secondary line-clamp-3 mb-4">
|
|
40
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.
|
|
41
|
+
</div>
|
|
42
|
+
<div class="grid grid-cols-[auto_1fr] mb-4 gap-x-4" n:if="$showAuthor">
|
|
43
|
+
<picture class="x-image before:skeleton size-12 rounded-full aspect-square shrink-0 row-span-2 overflow-hidden">
|
|
44
|
+
<img class="object-cover" src="{=placeholder(48,48)}" alt=" " width="48" height="48" loading="lazy">
|
|
45
|
+
</picture>
|
|
46
|
+
<span class="x-text sm font-medium self-end">Author Name</span>
|
|
47
|
+
<div class="flex gap-4">
|
|
48
|
+
<span class="x-text xs text-main-tertiary font-medium">04.12.2025</span>
|
|
49
|
+
<span class="x-text xs text-main-tertiary font-medium">5 min read</span>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<span class="x-button ghosted pl-1 mt-auto">
|
|
53
|
+
Read more
|
|
54
|
+
<svg class="me-auto size-5 group-hocus:translate-x-1 transition-transform" aria-hidden="true">
|
|
55
|
+
<use href="#heroicons-mini/arrow-right"/>
|
|
56
|
+
</svg>
|
|
57
|
+
</span>
|
|
58
|
+
</div>
|
|
59
|
+
</a>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<header class="x-header-nav-right h-(--x-header-height) grid grid-cols-container bg-body-primary sticky top-0 z-40">
|
|
2
|
+
<div class="flex items-center gap-1.5 md:gap-3">
|
|
3
|
+
<a href="#" class="shrink-0 max-lg:mr-auto lg:mr-7">
|
|
4
|
+
<img class="w-25 h-8" src="{placeholder(100,32)}" loading="lazy" alt="Logo">
|
|
5
|
+
</a>
|
|
6
|
+
<nav class="flex items-center flex-wrap justify-start gap-x-1 max-lg:hidden mr-auto">
|
|
7
|
+
<div class="x-popover trigger-hover group" n:foreach="range(1,4) as $item">
|
|
8
|
+
<a href="#" class="x-text x-link sm font-medium py-1 px-3 flex items-center gap-1.5">
|
|
9
|
+
Menu item {$iterator->counter}
|
|
10
|
+
<svg class="size-4 transition group-hocus:-scale-y-100" n:if="$iterator->last">
|
|
11
|
+
<use href="#heroicons-solid/chevron-down" />
|
|
12
|
+
</svg>
|
|
13
|
+
</a>
|
|
14
|
+
<div
|
|
15
|
+
n:class="
|
|
16
|
+
'x-nav-popover x-popover-content bottom-end border border-body-secondary shadow-lg min-w-55',
|
|
17
|
+
'm-1.5 p-4 flex flex-col gap-1 before:-top-2.5 before:h-2.5'
|
|
18
|
+
"
|
|
19
|
+
n:if="$iterator->last"
|
|
20
|
+
>
|
|
21
|
+
<a href="#" class="x-text x-link sm font-medium py-1 flex items-center gap-1.5" n:foreach="range(1,4) as $item">
|
|
22
|
+
Menu item
|
|
23
|
+
</a>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</nav>
|
|
27
|
+
<div class="x-popover trigger-focus group">
|
|
28
|
+
<button class="x-text x-link sm font-medium py-1 px-3 flex items-center gap-1.5" type="button" tabindex="0">
|
|
29
|
+
<img
|
|
30
|
+
class="aspect-4/3 size-4"
|
|
31
|
+
src="https://cdn.jsdelivr.net/npm/flag-icons@7.5.0/flags/1x1/gb.svg"
|
|
32
|
+
alt="at"
|
|
33
|
+
loading="lazy"
|
|
34
|
+
>
|
|
35
|
+
EN
|
|
36
|
+
<svg class="size-4 transition group-focus-within:-scale-y-100">
|
|
37
|
+
<use href="#heroicons-solid/chevron-down" />
|
|
38
|
+
</svg>
|
|
39
|
+
</button>
|
|
40
|
+
<div class="x-popover-content bottom-end border border-body-secondary shadow-lg m-1.5 p-4 flex flex-col gap-1">
|
|
41
|
+
<a href="#" class="x-text x-link sm font-medium py-1 flex items-center gap-1.5" n:foreach="range(1,4) as $item">
|
|
42
|
+
<img
|
|
43
|
+
class="aspect-4/3 size-4"
|
|
44
|
+
src="https://cdn.jsdelivr.net/npm/flag-icons@7.5.0/flags/1x1/gb.svg"
|
|
45
|
+
alt="at"
|
|
46
|
+
loading="lazy"
|
|
47
|
+
>
|
|
48
|
+
English
|
|
49
|
+
</a>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<a href="#" class="x-button sm max-md:hidden">
|
|
53
|
+
Call to action
|
|
54
|
+
</a>
|
|
55
|
+
<button
|
|
56
|
+
class="x-button square sm lg:hidden group swap"
|
|
57
|
+
type="button"
|
|
58
|
+
data-invoke-action="x-drawer#toggle"
|
|
59
|
+
data-invoke-target="#navDrawer"
|
|
60
|
+
aria-controls="navDrawer"
|
|
61
|
+
aria-expanded="false"
|
|
62
|
+
>
|
|
63
|
+
<svg class="size-5 shrink-0 not-group-aria-expanded:opacity-100 group-aria-expanded:rotate-45" aria-hidden="true">
|
|
64
|
+
<use href="#heroicons-solid/bars-3"></use>
|
|
65
|
+
</svg>
|
|
66
|
+
<svg class="size-5 shrink-0 not-group-aria-expanded:-rotate-45 group-aria-expanded:opacity-100" aria-hidden="true">
|
|
67
|
+
<use href="#heroicons-solid/x-mark"></use>
|
|
68
|
+
</svg>
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
</header>
|