front-end-dev-standards 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.
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # front-end-dev-standards
2
+
3
+ Company-wide Angular coding standards packaged for **Cursor AI** and **GitHub Copilot**.
4
+
5
+ ## What It Does
6
+
7
+ When installed in an Angular project, the postinstall script automatically:
8
+
9
+ 1. Creates `.ai-standards/` with standards markdown files
10
+ 2. Creates `.cursor/rules/company.mdc` for Cursor
11
+ 3. Creates `.github/copilot-instructions.md` for GitHub Copilot
12
+
13
+ ```
14
+ npm install -D front-end-dev-standards
15
+
16
+ postinstall runs setup.js
17
+
18
+ .ai-standards/ ← standards source of truth (local copy)
19
+ .cursor/rules/ ← Cursor reads this automatically
20
+ .github/ ← Copilot reads copilot-instructions.md
21
+
22
+ AI tools generate standardized Angular code
23
+ ```
24
+
25
+ ## Installation
26
+
27
+ ### From npm registry (production)
28
+
29
+ ```bash
30
+ npm install -D front-end-dev-standards
31
+ ```
32
+
33
+ ### Local development (this monorepo)
34
+
35
+ ```bash
36
+ # Already wired via file: dependency in root package.json
37
+ npm install
38
+ npm run standards:setup
39
+ ```
40
+
41
+ ## Manual Commands
42
+
43
+ ```bash
44
+ # Idempotent setup (skips existing files)
45
+ npm run standards:setup
46
+
47
+ # Force overwrite all generated files
48
+ npm run standards:setup:force
49
+
50
+ # Preview changes without writing
51
+ node packages/dev-standards/scripts/setup.js --dry-run
52
+ ```
53
+
54
+ ## Configuration
55
+
56
+ Create `.standardsrc.json` in your project root:
57
+
58
+ ```json
59
+ {
60
+ "overwrite": false,
61
+ "targets": {
62
+ "standardsDir": ".ai-standards",
63
+ "cursorRulesFile": ".cursor/rules/company.mdc",
64
+ "copilotInstructionsFile": ".github/copilot-instructions.md"
65
+ }
66
+ }
67
+ ```
68
+
69
+ | Option | Default | Description |
70
+ |---|---|---|
71
+ | `overwrite` | `false` | When `true`, replaces existing generated files |
72
+ | `targets.standardsDir` | `.ai-standards` | Where standards MD files are copied |
73
+ | `targets.cursorRulesFile` | `.cursor/rules/company.mdc` | Cursor rules output path |
74
+ | `targets.copilotInstructionsFile` | `.github/copilot-instructions.md` | Copilot instructions path |
75
+
76
+ CLI flag `--force` overrides `overwrite` to `true` for a single run.
77
+
78
+ ## Adding a New Standards File
79
+
80
+ 1. Create any `*.md` file in `packages/dev-standards/standards/` (e.g. `security.md`)
81
+ 2. Run `npm run standards:setup` (or `npm install` — postinstall runs automatically)
82
+
83
+ All `.md` files in `standards/` are **auto-discovered** — no need to edit `config/default.json`.
84
+
85
+ When a new file is added, Cursor and Copilot config files are refreshed automatically.
86
+ To update **existing** standards content, run `npm run standards:setup:force`.
87
+
88
+ ## Standards Files
89
+
90
+ | File | Purpose |
91
+ |---|---|
92
+ | `standards/angular.md` | Angular 20+ patterns: standalone, signals, inject, forms |
93
+ | `standards/architecture.md` | Feature-based folder structure, layers, data flow |
94
+ | `standards/coding-style.md` | TypeScript naming, formatting, review checklist |
95
+ | `standards/testing.md` | Vitest conventions, coverage expectations |
96
+
97
+ ## How Cursor Reads Rules
98
+
99
+ Cursor automatically loads `.cursor/rules/*.mdc` files from your project.
100
+
101
+ The generated `company.mdc` has `alwaysApply: true`, so every AI interaction in the project follows your standards.
102
+
103
+ ## How GitHub Copilot Reads Standards
104
+
105
+ Copilot reads `.github/copilot-instructions.md` at the repository root.
106
+
107
+ It does **not** read `.cursor/rules/` — that's why the postinstall script generates a separate Copilot file from the same standards.
108
+
109
+ ## Versioning & Upgrades
110
+
111
+ ```bash
112
+ npm update front-end-dev-standards
113
+ npm run standards:setup:force # refresh generated files
114
+ ```
115
+
116
+ ## Publishing as npm Package
117
+
118
+ Registry: **https://registry.npmjs.org/** (configured in `.npmrc` and `publishConfig`)
119
+
120
+ ```bash
121
+ # From repo root
122
+ npm login
123
+ npm run standards:validate
124
+ npm run standards:pack
125
+ npm run standards:publish
126
+
127
+ # Version bumps + publish
128
+ npm run standards:publish:patch
129
+ npm run standards:publish:minor
130
+ npm run standards:publish:major
131
+ ```
132
+
133
+ Consumer projects install with:
134
+
135
+ ```json
136
+ {
137
+ "devDependencies": {
138
+ "front-end-dev-standards": "^1.0.0"
139
+ }
140
+ }
141
+ ```
142
+
143
+ The package's own `postinstall` hook runs `setup.js` automatically on `npm install`.
144
+
145
+ ## Package Structure
146
+
147
+ ```
148
+ packages/dev-standards/
149
+ ├── config/
150
+ │ └── default.json
151
+ ├── scripts/
152
+ │ └── setup.js
153
+ ├── standards/
154
+ │ ├── angular.md
155
+ │ ├── architecture.md
156
+ │ ├── coding-style.md
157
+ │ └── testing.md
158
+ ├── templates/
159
+ │ ├── company.mdc.template
160
+ │ └── copilot-instructions.md.template
161
+ ├── package.json
162
+ └── README.md
163
+ ```
164
+
165
+ ## License
166
+
167
+ UNLICENSED — internal company use only.
@@ -0,0 +1,8 @@
1
+ {
2
+ "overwrite": false,
3
+ "targets": {
4
+ "standardsDir": ".ai-standards",
5
+ "cursorRulesFile": ".cursor/rules/company.mdc",
6
+ "copilotInstructionsFile": ".github/copilot-instructions.md"
7
+ }
8
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "front-end-dev-standards",
3
+ "version": "1.0.0",
4
+ "description": "Company-wide Angular coding standards for Cursor AI and GitHub Copilot",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "main": "./scripts/setup.js",
8
+ "bin": {
9
+ "front-end-dev-standards-setup": "./scripts/setup.js"
10
+ },
11
+ "files": [
12
+ "standards/",
13
+ "templates/",
14
+ "scripts/",
15
+ "config/",
16
+ "README.md",
17
+ ".npmrc"
18
+ ],
19
+ "scripts": {
20
+ "postinstall": "node scripts/setup.js",
21
+ "setup": "node scripts/setup.js",
22
+ "setup:force": "node scripts/setup.js --force",
23
+ "prepublishOnly": "node scripts/validate-package.js",
24
+ "pack:dry-run": "npm pack --dry-run"
25
+ },
26
+ "publishConfig": {
27
+ "registry": "https://registry.npmjs.org/",
28
+ "access": "public"
29
+ },
30
+ "engines": {
31
+ "node": ">=18.0.0"
32
+ },
33
+ "keywords": [
34
+ "angular",
35
+ "standards",
36
+ "cursor",
37
+ "copilot",
38
+ "ai"
39
+ ]
40
+ }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Run npm commands from the dev-standards package directory.
5
+ * Ensures pack/publish only includes package files (not the Angular workspace root).
6
+ *
7
+ * Usage (from repo root):
8
+ * node packages/dev-standards/scripts/npm-cmd.js pack --ignore-scripts
9
+ * node packages/dev-standards/scripts/npm-cmd.js publish --ignore-scripts
10
+ */
11
+
12
+ import { execSync } from 'node:child_process';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
+ const npmArgs = process.argv.slice(2);
18
+
19
+ if (npmArgs.length === 0) {
20
+ console.error('[dev-standards] Usage: npm-cmd.js <npm-args...>');
21
+ process.exit(1);
22
+ }
23
+
24
+ process.chdir(PACKAGE_ROOT);
25
+ execSync(`npm ${npmArgs.join(' ')}`, { stdio: 'inherit', shell: true });
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * dev-standards — postinstall setup script
5
+ *
6
+ * Copies company standards into the consuming Angular project and generates
7
+ * AI tool configuration files for Cursor and GitHub Copilot.
8
+ *
9
+ * Idempotent by default: existing files are skipped unless --force is passed
10
+ * or .standardsrc.json sets "overwrite": true.
11
+ *
12
+ * Usage:
13
+ * node scripts/setup.js # safe, idempotent install
14
+ * node scripts/setup.js --force # overwrite all generated files
15
+ * node scripts/setup.js --dry-run # preview without writing
16
+ */
17
+
18
+ import fs from 'node:fs';
19
+ import path from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = path.dirname(__filename);
24
+
25
+ /** Directory where this package lives (packages/dev-standards or node_modules/dev-standards) */
26
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
27
+
28
+ /** Parse CLI flags */
29
+ const args = process.argv.slice(2);
30
+ const FORCE = args.includes('--force');
31
+ const DRY_RUN = args.includes('--dry-run');
32
+ const VERBOSE = args.includes('--verbose') || args.includes('-v');
33
+
34
+ /**
35
+ * Resolve the consuming project root.
36
+ * Walks up from cwd until it finds a package.json that is NOT this standards package.
37
+ */
38
+ function findProjectRoot(startDir = process.cwd()) {
39
+ let current = path.resolve(startDir);
40
+
41
+ while (current !== path.dirname(current)) {
42
+ const pkgPath = path.join(current, 'package.json');
43
+ if (fs.existsSync(pkgPath)) {
44
+ try {
45
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
46
+ // If this package.json belongs to dev-standards, keep walking up
47
+ if (pkg.name === 'front-end-dev-standards' || pkg.name === 'dev-standards') {
48
+ current = path.dirname(current);
49
+ continue;
50
+ }
51
+ return current;
52
+ } catch {
53
+ // malformed package.json — keep searching
54
+ }
55
+ }
56
+ current = path.dirname(current);
57
+ }
58
+
59
+ return process.cwd();
60
+ }
61
+
62
+ /**
63
+ * Load merged configuration: package defaults + project .standardsrc.json
64
+ */
65
+ function loadConfig(projectRoot) {
66
+ const defaultsPath = path.join(PACKAGE_ROOT, 'config', 'default.json');
67
+ const defaults = JSON.parse(fs.readFileSync(defaultsPath, 'utf8'));
68
+
69
+ const rcPath = path.join(projectRoot, '.standardsrc.json');
70
+ if (!fs.existsSync(rcPath)) {
71
+ return { ...defaults, overwrite: FORCE || defaults.overwrite };
72
+ }
73
+
74
+ const rc = JSON.parse(fs.readFileSync(rcPath, 'utf8'));
75
+ return {
76
+ ...defaults,
77
+ ...rc,
78
+ targets: { ...defaults.targets, ...rc.targets },
79
+ overwrite: FORCE || rc.overwrite === true || defaults.overwrite === true,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Resolve which standards files to copy.
85
+ * Default: all *.md files in packages/dev-standards/standards/ (auto-discovered).
86
+ * Optional: set "standardsFiles" in .standardsrc.json to copy a specific subset only.
87
+ */
88
+ function resolveStandardsFiles(sourceStandardsDir, config) {
89
+ if (Array.isArray(config.standardsFiles) && config.standardsFiles.length > 0) {
90
+ return config.standardsFiles;
91
+ }
92
+
93
+ if (!fs.existsSync(sourceStandardsDir)) {
94
+ warn(`Standards directory not found: ${sourceStandardsDir}`);
95
+ return [];
96
+ }
97
+
98
+ return fs
99
+ .readdirSync(sourceStandardsDir)
100
+ .filter((file) => file.endsWith('.md') && fs.statSync(path.join(sourceStandardsDir, file)).isFile())
101
+ .sort();
102
+ }
103
+
104
+ /** Ensure directory exists */
105
+ function ensureDir(dirPath) {
106
+ if (!fs.existsSync(dirPath)) {
107
+ if (DRY_RUN) {
108
+ log(`[dry-run] mkdir ${dirPath}`);
109
+ return;
110
+ }
111
+ fs.mkdirSync(dirPath, { recursive: true });
112
+ log(`Created directory: ${rel(dirPath)}`);
113
+ }
114
+ }
115
+
116
+ /** Copy file if missing or overwrite enabled */
117
+ function copyFile(src, dest, overwrite) {
118
+ const destExists = fs.existsSync(dest);
119
+
120
+ if (destExists && !overwrite) {
121
+ log(`Skipped (exists): ${rel(dest)}`);
122
+ return false;
123
+ }
124
+
125
+ if (DRY_RUN) {
126
+ log(`[dry-run] copy ${rel(src)} → ${rel(dest)}${destExists ? ' (overwrite)' : ''}`);
127
+ return true;
128
+ }
129
+
130
+ ensureDir(path.dirname(dest));
131
+ fs.copyFileSync(src, dest);
132
+ log(`${destExists ? 'Updated' : 'Created'}: ${rel(dest)}`);
133
+ return true;
134
+ }
135
+
136
+ /** Generate file from template with token replacement */
137
+ function generateFromTemplate(templatePath, destPath, tokens, overwrite) {
138
+ if (!fs.existsSync(templatePath)) {
139
+ warn(`Template not found: ${rel(templatePath)}`);
140
+ return false;
141
+ }
142
+
143
+ const destExists = fs.existsSync(destPath);
144
+ if (destExists && !overwrite) {
145
+ log(`Skipped (exists): ${rel(destPath)}`);
146
+ return false;
147
+ }
148
+
149
+ let content = fs.readFileSync(templatePath, 'utf8');
150
+ for (const [key, value] of Object.entries(tokens)) {
151
+ content = content.replaceAll(`{{${key}}}`, value);
152
+ }
153
+
154
+ if (DRY_RUN) {
155
+ log(`[dry-run] generate ${rel(destPath)}${destExists ? ' (overwrite)' : ''}`);
156
+ return true;
157
+ }
158
+
159
+ ensureDir(path.dirname(destPath));
160
+ fs.writeFileSync(destPath, content, 'utf8');
161
+ log(`${destExists ? 'Updated' : 'Created'}: ${rel(destPath)}`);
162
+ return true;
163
+ }
164
+
165
+ /** Paths relative to project root for readable logs */
166
+ let projectRoot = process.cwd();
167
+ function rel(absPath) {
168
+ return path.relative(projectRoot, absPath) || absPath;
169
+ }
170
+
171
+ function log(message) {
172
+ console.log(`[dev-standards] ${message}`);
173
+ }
174
+
175
+ function warn(message) {
176
+ console.warn(`[dev-standards] WARN: ${message}`);
177
+ }
178
+
179
+ /**
180
+ * Main setup routine
181
+ */
182
+ function run() {
183
+ projectRoot = findProjectRoot();
184
+ const config = loadConfig(projectRoot);
185
+ const overwrite = config.overwrite === true;
186
+
187
+ log(`Project root: ${projectRoot}`);
188
+ log(`Mode: ${DRY_RUN ? 'dry-run' : overwrite ? 'overwrite' : 'idempotent'}`);
189
+
190
+ const standardsDir = path.join(projectRoot, config.targets.standardsDir);
191
+ const cursorRulesFile = path.join(projectRoot, config.targets.cursorRulesFile);
192
+ const copilotFile = path.join(projectRoot, config.targets.copilotInstructionsFile);
193
+
194
+ ensureDir(standardsDir);
195
+ ensureDir(path.dirname(cursorRulesFile));
196
+ ensureDir(path.dirname(copilotFile));
197
+
198
+ // 1. Copy standards markdown files (auto-discover all *.md in standards/)
199
+ const sourceStandardsDir = path.join(PACKAGE_ROOT, 'standards');
200
+ const standardsFiles = resolveStandardsFiles(sourceStandardsDir, config);
201
+
202
+ if (standardsFiles.length === 0) {
203
+ warn('No standards .md files found to copy.');
204
+ } else if (VERBOSE) {
205
+ log(`Standards files: ${standardsFiles.join(', ')}`);
206
+ }
207
+
208
+ let copiedCount = 0;
209
+ let anyNewStandard = false;
210
+
211
+ for (const file of standardsFiles) {
212
+ const src = path.join(sourceStandardsDir, file);
213
+ const dest = path.join(standardsDir, file);
214
+ const destExists = fs.existsSync(dest);
215
+
216
+ if (!fs.existsSync(src)) {
217
+ warn(`Standard file missing in package: ${file}`);
218
+ continue;
219
+ }
220
+
221
+ if (copyFile(src, dest, overwrite)) {
222
+ copiedCount++;
223
+ if (!destExists) {
224
+ anyNewStandard = true;
225
+ }
226
+ }
227
+ }
228
+
229
+ // 2. Generate Cursor rules (.mdc) — refresh when new standards are added or --force
230
+ const standardsRelativePaths = standardsFiles.map((f) =>
231
+ path.join(config.targets.standardsDir, f).replace(/\\/g, '/'),
232
+ );
233
+
234
+ const templateTokens = {
235
+ STANDARDS_VERSION: readPackageVersion(),
236
+ STANDARDS_PATHS: standardsRelativePaths.map((p) => `- \`${p}\``).join('\n'),
237
+ GENERATED_AT: new Date().toISOString(),
238
+ };
239
+
240
+ const regenAiConfig = overwrite || anyNewStandard || !fs.existsSync(cursorRulesFile);
241
+
242
+ generateFromTemplate(
243
+ path.join(PACKAGE_ROOT, 'templates', 'company.mdc.template'),
244
+ cursorRulesFile,
245
+ templateTokens,
246
+ regenAiConfig,
247
+ );
248
+
249
+ // 3. Generate GitHub Copilot instructions
250
+ generateFromTemplate(
251
+ path.join(PACKAGE_ROOT, 'templates', 'copilot-instructions.md.template'),
252
+ copilotFile,
253
+ templateTokens,
254
+ regenAiConfig,
255
+ );
256
+
257
+ log(`Setup complete. ${copiedCount} standard file(s) processed.`);
258
+ log(`Cursor rules: ${rel(cursorRulesFile)}`);
259
+ log(`Copilot instructions: ${rel(copilotFile)}`);
260
+
261
+ if (!overwrite && !anyNewStandard) {
262
+ log('Tip: run with --force or set "overwrite": true in .standardsrc.json to refresh existing standards files.');
263
+ }
264
+ }
265
+
266
+ function readPackageVersion() {
267
+ try {
268
+ const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
269
+ return pkg.version ?? 'unknown';
270
+ } catch {
271
+ return 'unknown';
272
+ }
273
+ }
274
+
275
+ try {
276
+ run();
277
+ } catch (error) {
278
+ console.error('[dev-standards] Setup failed:', error.message);
279
+ process.exit(1);
280
+ }
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Pre-publish validation — ensures required package files exist before npm publish.
5
+ */
6
+
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
12
+
13
+ const REQUIRED_PATHS = [
14
+ 'package.json',
15
+ 'README.md',
16
+ 'scripts/setup.js',
17
+ 'config/default.json',
18
+ 'templates/company.mdc.template',
19
+ 'templates/copilot-instructions.md.template',
20
+ 'standards/angular.md',
21
+ 'standards/architecture.md',
22
+ 'standards/coding-style.md',
23
+ 'standards/testing.md',
24
+ ];
25
+
26
+ const missing = REQUIRED_PATHS.filter((rel) => !fs.existsSync(path.join(PACKAGE_ROOT, rel)));
27
+
28
+ if (missing.length > 0) {
29
+ console.error('[dev-standards] Publish validation failed. Missing files:');
30
+ missing.forEach((file) => console.error(` - ${file}`));
31
+ process.exit(1);
32
+ }
33
+
34
+ const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
35
+
36
+ if (pkg.private === true) {
37
+ console.error('[dev-standards] Publish validation failed: "private": true must be removed from package.json');
38
+ process.exit(1);
39
+ }
40
+
41
+ console.log(`[dev-standards] Publish validation passed (v${pkg.version}).`);