@open-rlb/ng-bootstrap 3.3.45 → 3.3.47
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/package.json +1 -1
- package/schematics/collection.json +5 -0
- package/schematics/ng-add/index.js +38 -8
- package/schematics/ng-add/schema.json +5 -0
- package/schematics/sync-skills/index.js +141 -0
- package/schematics/sync-skills/schema.js +2 -0
- package/schematics/sync-skills/schema.json +14 -0
- /package/schematics/{ng-add → sync-skills}/claude-skills/date-tz/SKILL.md +0 -0
- /package/schematics/{ng-add → sync-skills}/claude-skills/rlb-calendar/SKILL.md +0 -0
- /package/schematics/{ng-add → sync-skills}/claude-skills/rlb-components/SKILL.md +0 -0
- /package/schematics/{ng-add → sync-skills}/claude-skills/rlb-datatable/SKILL.md +0 -0
- /package/schematics/{ng-add → sync-skills}/claude-skills/rlb-design/SKILL.md +0 -0
- /package/schematics/{ng-add → sync-skills}/claude-skills/rlb-inputs/SKILL.md +0 -0
- /package/schematics/{ng-add → sync-skills}/claude-skills/rlb-modals/SKILL.md +0 -0
package/package.json
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
"description": "Add @open-rlb/ng-bootstrap to an Angular project: install peer dependencies, register Bootstrap styles, wire up provideRlbBootstrap(), and scaffold a starter component.",
|
|
6
6
|
"factory": "./ng-add/index#ngAdd",
|
|
7
7
|
"schema": "./ng-add/schema.json"
|
|
8
|
+
},
|
|
9
|
+
"sync-skills": {
|
|
10
|
+
"description": "Copy the Claude skills bundled with the installed version of @open-rlb/ng-bootstrap into .claude/skills. Re-runnable: this is how consumers pick up skill updates after npm update.",
|
|
11
|
+
"factory": "./sync-skills/index#syncSkills",
|
|
12
|
+
"schema": "./sync-skills/schema.json"
|
|
8
13
|
}
|
|
9
14
|
}
|
|
10
15
|
}
|
|
@@ -16,6 +16,8 @@ const DEPENDENCIES = [
|
|
|
16
16
|
{ name: 'bootstrap-icons', version: '^1.11.0', type: utility_1.DependencyType.Default },
|
|
17
17
|
{ name: '@types/bootstrap', version: '^5.2.0', type: utility_1.DependencyType.Dev },
|
|
18
18
|
];
|
|
19
|
+
/** Keeps `.claude/skills` in step with the installed library version on every `npm install`. */
|
|
20
|
+
const SYNC_SKILLS_COMMAND = 'ng g @open-rlb/ng-bootstrap:sync-skills';
|
|
19
21
|
/** Global styles required for the Bootstrap look & feel. */
|
|
20
22
|
const STYLE_PATHS = [
|
|
21
23
|
'node_modules/bootstrap/dist/css/bootstrap.min.css',
|
|
@@ -34,8 +36,10 @@ function ngAdd(options) {
|
|
|
34
36
|
// 4. Optionally scaffold a starter component.
|
|
35
37
|
options.skipStarter ? noop : scaffoldStarter(tree, project),
|
|
36
38
|
// 5. Optionally copy the bundled Claude skills into .claude/skills.
|
|
37
|
-
options.skipSkills ? noop :
|
|
38
|
-
// 6.
|
|
39
|
+
options.skipSkills ? noop : (0, schematics_1.schematic)('sync-skills', {}),
|
|
40
|
+
// 6. Optionally keep them in sync on every future `npm install`.
|
|
41
|
+
options.skipSkills || options.skipSkillsAutoSync ? noop : addSkillsPostinstall(),
|
|
42
|
+
// 7. Print next steps.
|
|
39
43
|
logNextSteps(project, options),
|
|
40
44
|
]);
|
|
41
45
|
};
|
|
@@ -91,13 +95,34 @@ function scaffoldStarter(tree, project) {
|
|
|
91
95
|
};
|
|
92
96
|
}
|
|
93
97
|
/**
|
|
94
|
-
*
|
|
95
|
-
* `.claude/skills`
|
|
96
|
-
*
|
|
98
|
+
* Adds a `postinstall` script that re-runs the sync-skills schematic, so `npm update` alone
|
|
99
|
+
* refreshes `.claude/skills` to match the newly installed library version.
|
|
100
|
+
*
|
|
101
|
+
* The script deliberately lives in the consumer's package.json rather than the library's: a
|
|
102
|
+
* library-side install script is silently skipped under `--ignore-scripts`, has to guess the
|
|
103
|
+
* app root via INIT_CWD, and would fire in unrelated repos on transitive installs.
|
|
97
104
|
*/
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
function addSkillsPostinstall() {
|
|
106
|
+
return (tree, context) => {
|
|
107
|
+
const raw = tree.read('/package.json');
|
|
108
|
+
if (!raw) {
|
|
109
|
+
return tree;
|
|
110
|
+
}
|
|
111
|
+
const pkg = JSON.parse(raw.toString('utf-8'));
|
|
112
|
+
const existing = pkg.scripts?.['postinstall'];
|
|
113
|
+
if (existing?.includes(SYNC_SKILLS_COMMAND)) {
|
|
114
|
+
return tree;
|
|
115
|
+
}
|
|
116
|
+
// Never rewrite a postinstall the consumer already relies on — tell them what to append.
|
|
117
|
+
if (existing) {
|
|
118
|
+
context.logger.warn(`⚠ A "postinstall" script already exists. To keep the Claude skills up to date, append:\n` +
|
|
119
|
+
` && ${SYNC_SKILLS_COMMAND}`);
|
|
120
|
+
return tree;
|
|
121
|
+
}
|
|
122
|
+
pkg.scripts = { ...pkg.scripts, postinstall: SYNC_SKILLS_COMMAND };
|
|
123
|
+
tree.overwrite('/package.json', JSON.stringify(pkg, null, 2) + '\n');
|
|
124
|
+
return tree;
|
|
125
|
+
};
|
|
101
126
|
}
|
|
102
127
|
function logNextSteps(project, options) {
|
|
103
128
|
return (_tree, context) => {
|
|
@@ -113,6 +138,11 @@ function logNextSteps(project, options) {
|
|
|
113
138
|
}
|
|
114
139
|
if (!options.skipSkills) {
|
|
115
140
|
log.info(' • Claude skills copied to .claude/skills/ (date-tz, rlb-* component guides)');
|
|
141
|
+
if (!options.skipSkillsAutoSync) {
|
|
142
|
+
log.info(` • "postinstall": "${SYNC_SKILLS_COMMAND}" added to package.json`);
|
|
143
|
+
log.info(' Skills refresh on every `npm install`. Note `npm update <pkg>` skips');
|
|
144
|
+
log.info(' root lifecycle scripts — follow it with a bare `npm install`.');
|
|
145
|
+
}
|
|
116
146
|
}
|
|
117
147
|
log.info('');
|
|
118
148
|
};
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"type": "boolean",
|
|
21
21
|
"default": false,
|
|
22
22
|
"description": "Do not copy the bundled Claude skills into .claude/skills."
|
|
23
|
+
},
|
|
24
|
+
"skipSkillsAutoSync": {
|
|
25
|
+
"type": "boolean",
|
|
26
|
+
"default": false,
|
|
27
|
+
"description": "Do not add a postinstall script that refreshes .claude/skills on future installs."
|
|
23
28
|
}
|
|
24
29
|
},
|
|
25
30
|
"required": []
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.syncSkills = syncSkills;
|
|
4
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
5
|
+
const rxjs_1 = require("rxjs");
|
|
6
|
+
/** Where the consumer's Claude skills live. */
|
|
7
|
+
const SKILLS_ROOT = '/.claude/skills';
|
|
8
|
+
/**
|
|
9
|
+
* Records which skill folders this library owns, so a later sync can delete the ones it no
|
|
10
|
+
* longer ships without touching skills the consumer wrote themselves.
|
|
11
|
+
*/
|
|
12
|
+
const MANIFEST_PATH = `${SKILLS_ROOT}/.rlb-skills.json`;
|
|
13
|
+
/** Set this in CI if a pipeline asserts a clean working tree after `npm install`. */
|
|
14
|
+
const SKIP_ENV_VAR = 'RLB_SKIP_SKILL_SYNC';
|
|
15
|
+
const PACKAGE_NAME = '@open-rlb/ng-bootstrap';
|
|
16
|
+
/**
|
|
17
|
+
* Copies the Claude skills bundled with the *installed* version of the library into the
|
|
18
|
+
* consumer's `.claude/skills`. Safe to re-run: it is how consumers pick up skill changes after
|
|
19
|
+
* `npm update`, typically from their own `postinstall` script.
|
|
20
|
+
*/
|
|
21
|
+
function syncSkills(options) {
|
|
22
|
+
return async (tree, context) => {
|
|
23
|
+
if (process.env[SKIP_ENV_VAR]) {
|
|
24
|
+
context.logger.info(`• ${SKIP_ENV_VAR} is set — skipping Claude skill sync.`);
|
|
25
|
+
return noop;
|
|
26
|
+
}
|
|
27
|
+
const bundled = await readBundledSkills(context);
|
|
28
|
+
if (bundled.skills.length === 0) {
|
|
29
|
+
context.logger.warn(`⚠ ${PACKAGE_NAME} shipped without Claude skills — nothing to sync. ` +
|
|
30
|
+
'This usually means the package was built with a bare `ng build` instead of `npm run lib:build`.');
|
|
31
|
+
return noop;
|
|
32
|
+
}
|
|
33
|
+
const previous = readManifest(tree);
|
|
34
|
+
// Only folders a previous sync claimed are ours to delete. Anything else in .claude/skills
|
|
35
|
+
// was authored in the consumer and must survive.
|
|
36
|
+
const stale = options.prune === false
|
|
37
|
+
? []
|
|
38
|
+
: (previous?.skills ?? []).filter(name => !bundled.skills.includes(name));
|
|
39
|
+
const added = bundled.files.filter(file => !tree.exists(join(SKILLS_ROOT, file.path)));
|
|
40
|
+
const updated = bundled.files.filter(file => {
|
|
41
|
+
const target = join(SKILLS_ROOT, file.path);
|
|
42
|
+
const current = tree.read(target);
|
|
43
|
+
return current !== null && !current.equals(file.content);
|
|
44
|
+
});
|
|
45
|
+
return (0, schematics_1.chain)([
|
|
46
|
+
prune(stale),
|
|
47
|
+
(0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)('./claude-skills'), [(0, schematics_1.move)(SKILLS_ROOT)]), schematics_1.MergeStrategy.Overwrite),
|
|
48
|
+
writeManifest(bundled.skills),
|
|
49
|
+
logSummary({ skills: bundled.skills, added: added.length, updated: updated.length, stale }),
|
|
50
|
+
]);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Reads the skills packaged alongside this schematic (see scripts/build-schematics.mjs). */
|
|
54
|
+
async function readBundledSkills(context) {
|
|
55
|
+
const source = (0, schematics_1.url)('./claude-skills')(context);
|
|
56
|
+
const bundledTree = (0, rxjs_1.isObservable)(source) ? await (0, rxjs_1.firstValueFrom)(source) : source;
|
|
57
|
+
const skills = new Set();
|
|
58
|
+
const files = [];
|
|
59
|
+
bundledTree.visit((path, entry) => {
|
|
60
|
+
const folder = path.replace(/^\//, '').split('/')[0];
|
|
61
|
+
if (folder) {
|
|
62
|
+
skills.add(folder);
|
|
63
|
+
}
|
|
64
|
+
if (entry) {
|
|
65
|
+
files.push({ path, content: entry.content });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return { skills: [...skills].sort(), files };
|
|
69
|
+
}
|
|
70
|
+
function readManifest(tree) {
|
|
71
|
+
const raw = tree.read(MANIFEST_PATH);
|
|
72
|
+
if (!raw) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(raw.toString('utf-8'));
|
|
77
|
+
return Array.isArray(parsed.skills)
|
|
78
|
+
? { package: PACKAGE_NAME, version: '', ...parsed, skills: parsed.skills }
|
|
79
|
+
: null;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// A hand-mangled manifest must not abort the sync — treat it as "nothing owned yet".
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Deletes every file under the named skill folders. */
|
|
87
|
+
function prune(stale) {
|
|
88
|
+
return (tree) => {
|
|
89
|
+
for (const name of stale) {
|
|
90
|
+
const dir = tree.getDir(join(SKILLS_ROOT, name));
|
|
91
|
+
const paths = [];
|
|
92
|
+
dir.visit(path => paths.push(path));
|
|
93
|
+
paths.forEach(path => tree.delete(path));
|
|
94
|
+
}
|
|
95
|
+
return tree;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function writeManifest(skills) {
|
|
99
|
+
return (tree) => {
|
|
100
|
+
const manifest = { package: PACKAGE_NAME, version: installedVersion(), skills };
|
|
101
|
+
const content = JSON.stringify(manifest, null, 2) + '\n';
|
|
102
|
+
if (tree.exists(MANIFEST_PATH)) {
|
|
103
|
+
tree.overwrite(MANIFEST_PATH, content);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
tree.create(MANIFEST_PATH, content);
|
|
107
|
+
}
|
|
108
|
+
return tree;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The version of the library these skills came from. Resolved from the package.json two levels
|
|
113
|
+
* up from the compiled `schematics/sync-skills/index.js`, i.e. the installed package's own.
|
|
114
|
+
*/
|
|
115
|
+
function installedVersion() {
|
|
116
|
+
try {
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
118
|
+
return require('../../package.json').version ?? 'unknown';
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return 'unknown';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function logSummary(summary) {
|
|
125
|
+
return (_tree, context) => {
|
|
126
|
+
const log = context.logger;
|
|
127
|
+
log.info('');
|
|
128
|
+
log.info(`✅ Claude skills synced from ${PACKAGE_NAME}@${installedVersion()}`);
|
|
129
|
+
log.info(` • ${summary.skills.length} skills: ${summary.skills.join(', ')}`);
|
|
130
|
+
log.info(` • ${summary.added} new file(s), ${summary.updated} updated`);
|
|
131
|
+
if (summary.stale.length) {
|
|
132
|
+
log.info(` • pruned (no longer shipped): ${summary.stale.join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
log.info('');
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function join(base, path) {
|
|
138
|
+
return `${base}/${path}`.replace(/\/+/g, '/');
|
|
139
|
+
}
|
|
140
|
+
/** A no-op rule. */
|
|
141
|
+
const noop = tree => tree;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "OpenRlbNgBootstrapSyncSkills",
|
|
4
|
+
"title": "@open-rlb/ng-bootstrap sync-skills schematic",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"prune": {
|
|
8
|
+
"type": "boolean",
|
|
9
|
+
"default": true,
|
|
10
|
+
"description": "Delete skills the library previously shipped but no longer does. Skills authored in the consumer are never touched."
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"required": []
|
|
14
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|