@memberjunction/standards 0.0.0 → 6.1.0-edge.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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2023 MemberJunction
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,137 @@
1
1
  # @memberjunction/standards
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ MemberJunction's engineering standards, as runnable checks — plus the scaffolding that gets a
4
+ repository actually enforcing them.
4
5
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
+ ```bash
7
+ npx mj-standards adopt --ci github --declare-compliant # set a repo up
8
+ npx mj-standards check # run what it adopted
9
+ npx mj-standards list # what exists, and this repo's stance
10
+ ```
6
11
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
12
+ Inside a repo that has the MJ CLI, the same commands are `mj standards adopt` / `check` / `list`.
8
13
 
9
- ## Purpose
14
+ ---
10
15
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/standards`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
16
+ ## Why this package exists
15
17
 
16
- ## What is OIDC Trusted Publishing?
18
+ MJ has two kinds of standards and they need opposite distribution mechanisms.
17
19
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
20
+ **Judgment standards** are prose that has to be read the guides in the MJ repo. They are
21
+ distributed as documentation.
19
22
 
20
- ## Setup Instructions
23
+ **Executable standards** are the ones a machine can settle. Those are here, versioned like code,
24
+ because copy-pasting a check script into each repo stops scaling at about five repos and guarantees
25
+ that half of them are running a version from eight months ago.
21
26
 
22
- To properly configure OIDC trusted publishing for this package:
27
+ ## The property that makes it safe
23
28
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
29
+ > **Adding a standard to this package never changes an existing repository's result.**
28
30
 
29
- ## DO NOT USE THIS PACKAGE
31
+ Three mechanisms, together:
30
32
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
33
+ 1. **Opt-in per check.** A check does nothing until a repo's `.mj-standards.json` names it.
34
+ Registration makes a check *available*, not active.
35
+ 2. **Version-pinned adoption.** Each check declares the MJ version it was introduced in (`Since`).
36
+ Each repo records the version it adopted against (`StandardsVersion`). Checks newer than that
37
+ are reported as available and are **not run** — upgrading this package cannot activate them.
38
+ 3. **Severity lives in the repo, not the package.** `DefaultSeverity` is what `adopt` writes for a
39
+ *new* adopter. Changing it here never changes a repo that has already adopted. Severity can
40
+ decay forward — `warn` → `error` on a major, by the repo's own choice — and never backward into
41
+ something already shipped.
36
42
 
37
- ## More Information
43
+ The result: this package can ship new standards continuously, and a repo pinned on an older MJ
44
+ never wakes up to a red build it did not ask for. Adopting a new standard is always a visible,
45
+ reviewable commit.
38
46
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
47
+ ## `.mj-standards.json`
42
48
 
43
- ---
49
+ ```jsonc
50
+ {
51
+ "$schema": "./node_modules/@memberjunction/standards/schema/mj-standards.schema.json",
52
+ "StandardsVersion": "6.0.0", // what this repo adopted against
53
+ "Checks": {
54
+ "ui-layers": {
55
+ "Severity": "error", // off | warn | error
56
+ "Roots": ["packages"],
57
+ "Options": {
58
+ // Locked subtrees: an undeclared package HERE is a failure. Everywhere else it is
59
+ // skipped. This is the shape a real migration takes — one tree cleaned and held, the
60
+ // rest still being worked through.
61
+ "requireDeclaredIn": ["packages/Angular"]
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ A check absent from `Checks` does not run. A check present but `off` does not run and is not
69
+ nagged about — the repo has seen it and said no.
70
+
71
+ ## Commands
72
+
73
+ ### `adopt`
74
+
75
+ Writes the config, and optionally a CI workflow, an npm script, and the per-package declarations.
76
+ **Idempotent and additive**: it never lowers a severity you raised, never overwrites a CI file you
77
+ edited, and never bumps `StandardsVersion` without `--upgrade`.
78
+
79
+ | Flag | |
80
+ |---|---|
81
+ | `--ci github` | write `.github/workflows/mj-standards.yml` |
82
+ | `--declare-compliant` | declare `mjUILayer` on packages that already pass |
83
+ | `--upgrade` | enable standards newer than the recorded version, and bump it |
84
+ | `--dry-run` | report, write nothing |
85
+
86
+ **`--declare-compliant` matters more than it looks.** Without it, a fresh adoption produces a
87
+ config that enforces nothing: every package is undeclared, so every package is skipped, and the
88
+ repo gets a green check that means nothing.
89
+
90
+ It takes the **strictest** layer each package honestly qualifies for and **never assigns `shell`**.
91
+ `shell` checks nothing, so every package passes as `shell` — assigning it would hand a permanent
92
+ exemption to exactly the packages that need work. (An earlier version did assign it; a two-package
93
+ test repo caught it in the first run, with the deliberately-broken package coming back declared
94
+ `shell` and passing.) A package that qualifies for nothing is reported, not declared.
95
+
96
+ ### `check`
97
+
98
+ Runs the adopted standards. Exit 1 on `error` violations only; `--strict` also fails on warnings —
99
+ the flag to turn on once a newly adopted check is clean.
100
+
101
+ ### `list`
102
+
103
+ Every registered standard, when it was introduced, and what this repo does with it.
104
+
105
+ ## The standards
106
+
107
+ | Id | Since | What it enforces |
108
+ |---|---|---|
109
+ | `ui-layers` | 6.0.0 | The four-layer UI architecture — [guide](https://github.com/MemberJunction/MJ/blob/next/guides/UI_LAYERING_GUIDE.md). Widgets may not import `@angular/router` or MJ Explorer, and may not construct a global-provider `RunView`/`Metadata`. Packages opt in with `"mjUILayer"` in their own `package.json`. |
110
+
111
+ ## Adding a standard
44
112
 
45
- **Maintained for OIDC setup purposes only**
113
+ 1. Implement `StandardCheck` in `src/checks/`.
114
+ 2. Set `Since` to the MJ version it will **ship in**. Never backdate it — that would silently
115
+ activate the check in repos that adopted before it existed.
116
+ 3. Register it in `src/registry.ts`.
117
+ 4. Give it a `DocsUrl`. Every failure prints it; a rule whose reasoning is one click away gets
118
+ followed, and one that just says "no" gets worked around.
119
+ 5. Consider shipping it at `DefaultSeverity: 'warn'` first if it is likely to have a long tail.
120
+
121
+ ## Design notes
122
+
123
+ **No runtime dependencies.** This gets installed into client repos and run in CI; every dependency
124
+ is one more thing that can conflict with their tree. The only non-trivial thing it needed was
125
+ semver comparison, which is twenty lines.
126
+
127
+ **Comments are stripped before matching.** MJ source documents itself heavily — a JSDoc block
128
+ explaining "this calls `new RunView()` on the global provider" is a comment about a violation, not
129
+ a violation. A gate that cannot tell the difference gets switched off.
130
+
131
+ **Only zero-argument constructors are flagged.** `new RunView(provider)` passes a provider
132
+ explicitly and is correct. An earlier, blunter pattern flagged it; false positives are how a gate
133
+ loses its authority.
134
+
135
+ **Reviewed exceptions** use a marker in a comment on the offending line, or the line directly
136
+ above it — one line, so a marker cannot drift away from what it excuses. For `ui-layers` the marker
137
+ is `mj-ui-layers-allow`.
package/bin/run.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `mj-standards` — the standards runner, without the MJ CLI.
4
+ *
5
+ * Client and external repos should not have to install the whole MemberJunction CLI to run four
6
+ * checks in CI. This binary is the same code the `mj standards` commands call, exposed directly:
7
+ *
8
+ * npx mj-standards check
9
+ * npx mj-standards adopt --ci github --declare-compliant
10
+ * npx mj-standards list
11
+ *
12
+ * Argument handling is deliberately minimal — anyone who wants rich flags can install the CLI.
13
+ */
14
+ import { LoadConfig, HasConfig, RunStandards, FormatSummary, ExitCodeFor, Adopt, STANDARD_CHECKS, IsNewerThan } from '../dist/index.js';
15
+ import { readFileSync } from 'node:fs';
16
+ import { dirname, join } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+
19
+ const argv = process.argv.slice(2);
20
+ const command = argv[0] ?? 'check';
21
+ const repoRoot = process.cwd();
22
+ const flag = (name) => argv.includes(`--${name}`);
23
+ const value = (name) => {
24
+ const i = argv.indexOf(`--${name}`);
25
+ return i !== -1 ? argv[i + 1] : undefined;
26
+ };
27
+
28
+ const ownVersion = JSON.parse(
29
+ readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8'),
30
+ ).version;
31
+
32
+ function usage() {
33
+ console.log(`mj-standards <check|adopt|list> [options]
34
+
35
+ check run the standards this repo has adopted
36
+ --strict treat warnings as errors
37
+ --quiet summary line only
38
+
39
+ adopt write .mj-standards.json and optional scaffolding
40
+ --ci github also write a GitHub Actions workflow
41
+ --declare-compliant declare mjUILayer on packages that already pass
42
+ --upgrade enable standards newer than the repo's recorded version
43
+ --dry-run report, write nothing
44
+
45
+ list every standard, and this repo's stance on each`);
46
+ }
47
+
48
+ if (flag('help') || command === 'help') {
49
+ usage();
50
+ process.exit(0);
51
+ }
52
+
53
+ if (command === 'check') {
54
+ if (!HasConfig(repoRoot)) {
55
+ console.error('No .mj-standards.json here. Run `npx mj-standards adopt` first.');
56
+ process.exit(2);
57
+ }
58
+ const config = LoadConfig(repoRoot);
59
+ const summary = await RunStandards(repoRoot, config);
60
+ if (!flag('quiet')) console.log(FormatSummary(summary, config));
61
+ const failed = ExitCodeFor(summary) !== 0 || (flag('strict') && summary.WarningCount > 0);
62
+ process.exit(failed ? 1 : 0);
63
+ } else if (command === 'adopt') {
64
+ const result = Adopt({
65
+ RepoRoot: repoRoot,
66
+ Version: value('version') ?? ownVersion,
67
+ Upgrade: flag('upgrade'),
68
+ DryRun: flag('dry-run'),
69
+ Ci: value('ci') === 'github' ? 'github' : 'none',
70
+ AddNpmScript: !flag('no-npm-script'),
71
+ DeclareCompliant: flag('declare-compliant'),
72
+ });
73
+ const icon = { created: '+', updated: '~', skipped: '·' };
74
+ for (const a of result.Actions) console.log(`${icon[a.Kind]} ${a.What}${a.Detail ? ` — ${a.Detail}` : ''}`);
75
+ console.log(flag('dry-run') ? '\nDry run — nothing was written.' : `\nAdopted at StandardsVersion ${result.Config.StandardsVersion}.`);
76
+ } else if (command === 'list') {
77
+ const config = HasConfig(repoRoot) ? LoadConfig(repoRoot) : null;
78
+ for (const check of STANDARD_CHECKS) {
79
+ const entry = config?.Checks[check.Id];
80
+ const stance = !config
81
+ ? 'not adopted'
82
+ : entry
83
+ ? entry.Severity
84
+ : IsNewerThan(check.Since, config.StandardsVersion)
85
+ ? `available (added in ${check.Since}, after this repo adopted)`
86
+ : 'available, not adopted';
87
+ console.log(`${check.Id} [${stance}] since ${check.Since}\n ${check.Title}\n`);
88
+ }
89
+ } else {
90
+ usage();
91
+ process.exit(2);
92
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @fileoverview The UI layering standard.
3
+ *
4
+ * Enforces the boundaries in the MJ repo's `guides/UI_LAYERING_GUIDE.md`:
5
+ *
6
+ * runtime (L0) pure TS — no Angular at all
7
+ * widgets (L1+L2) framework-clean Angular — no Router, no Explorer, no global provider
8
+ * surface (L3) Explorer surfaces — NavigationService only, never Router
9
+ * shell the navigation layer ITSELF — the documented Router exception
10
+ *
11
+ * A package opts in by declaring `"mjUILayer"` in its own package.json. A package without the
12
+ * field is skipped, unless `requireDeclared` is on — see the option's docs for why that matters.
13
+ *
14
+ * @module @memberjunction/standards
15
+ */
16
+ import type { StandardCheck } from '../types.js';
17
+ /** The layers a package can declare. */
18
+ export type UILayer = 'runtime' | 'widgets' | 'surface' | 'shell';
19
+ /**
20
+ * Blank out `//` and block comments, preserving every newline so line numbers still line up.
21
+ *
22
+ * Necessary because MJ source documents itself heavily: a JSDoc block explaining "this calls
23
+ * `new RunView()` on the global provider" is a comment about a violation, not a violation, and a
24
+ * gate that cannot tell the difference gets switched off. String literals are left alone — the
25
+ * banned constructs do not plausibly appear in one.
26
+ */
27
+ export declare function StripComments(source: string): string;
28
+ /** One import/export-from occurrence. */
29
+ export interface ImportRecord {
30
+ Specifier: string;
31
+ Names: string[];
32
+ Line: number;
33
+ }
34
+ /**
35
+ * Extract every import/export-from specifier with the names it binds and its 1-based line.
36
+ *
37
+ * Import-aware rather than grep-based: matching the bare word `Router` anywhere produces false
38
+ * positives on `RouterLikeThing`, on comments and on strings. What matters is whether the module
39
+ * *binds* the symbol.
40
+ */
41
+ export declare function ParseImports(source: string): ImportRecord[];
42
+ /**
43
+ * Does the given 1-based line carry a reviewed-exception marker, on itself or the line above?
44
+ *
45
+ * The preceding line counts because a real exception deserves a sentence of explanation and a
46
+ * trailing comment long enough to hold one is unreadable. Only ONE line above — a wider window
47
+ * would let a marker drift away from the thing it excuses.
48
+ */
49
+ export declare function IsAllowed(lines: string[], lineNumber: number): boolean;
50
+ /** The registered UI layering standard. */
51
+ export declare const UILayersCheck: StandardCheck;
52
+ /** A package that has not yet declared a layer, and whether it would pass if it did. */
53
+ export interface UndeclaredPackage {
54
+ /** Absolute directory. */
55
+ Dir: string;
56
+ Name: string;
57
+ /**
58
+ * Layers this package could honestly claim, strictest-first, never including `shell`.
59
+ * Empty means it needs work before it can be declared at all.
60
+ */
61
+ WouldPassAs: UILayer[];
62
+ }
63
+ /**
64
+ * Find packages with no `mjUILayer` and work out which layers they could honestly claim.
65
+ *
66
+ * This is what lets `mj standards adopt` declare the already-compliant packages instead of leaving
67
+ * a repo with a config that enforces nothing. It answers the question by **running the rules**
68
+ * against a hypothetical layer, not by editing anything — probing by writing a field to
69
+ * package.json and reverting it would be a data-loss bug waiting for an interrupted process.
70
+ *
71
+ * `WouldPassAs` is ordered strictest-first and never contains `shell`, so a caller taking
72
+ * `WouldPassAs[0]` gets the most informative honest label, and a package that passes nothing comes
73
+ * back with an empty array — "needs work", not "call it shell".
74
+ */
75
+ export declare function ProbeUndeclaredPackages(repoRoot: string, roots: string[]): UndeclaredPackage[];
76
+ /** Write `mjUILayer` into a package's manifest, preserving formatting as best as JSON allows. */
77
+ export declare function DeclareLayer(packageDir: string, layer: UILayer): void;
78
+ //# sourceMappingURL=ui-layers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-layers.d.ts","sourceRoot":"","sources":["../../src/checks/ui-layers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAA6B,aAAa,EAAa,MAAM,aAAa,CAAC;AAEvF,wCAAwC;AACxC,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AA6ElE;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAqBpD;AAED,yCAAyC;AACzC,MAAM,WAAW,YAAY;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAoB3D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAItE;AAiID,2CAA2C;AAC3C,eAAO,MAAM,aAAa,EAAE,aA2F3B,CAAC;AAMF,wFAAwF;AACxF,MAAM,WAAW,iBAAiB;IAC9B,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,WAAW,EAAE,OAAO,EAAE,CAAC;CAC1B;AAYD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,iBAAiB,EAAE,CAa9F;AAED,iGAAiG;AACjG,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAKrE"}