@astryxdesign/core 0.7.2-canary.e868278 → 0.7.2-canary.f2b60a0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.2-canary.e868278",
3
+ "version": "0.7.2-canary.f2b60a0",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -792,8 +792,8 @@
792
792
  "react-dom": ">=19.0.0"
793
793
  },
794
794
  "devDependencies": {
795
- "@astryxdesign/a11y-spec": "0.7.2-canary.e868278",
796
- "@astryxdesign/cli": "0.7.2-canary.e868278",
795
+ "@astryxdesign/a11y-spec": "0.7.2-canary.f2b60a0",
796
+ "@astryxdesign/cli": "0.7.2-canary.f2b60a0",
797
797
  "@babel/cli": "^7.29.7",
798
798
  "@babel/core": "^7.29.7",
799
799
  "@babel/preset-react": "^7.29.7",
@@ -106,6 +106,44 @@ export function isAstryxInitialized(targetDir = process.cwd()) {
106
106
  return false;
107
107
  }
108
108
 
109
+ /**
110
+ * Does an agent working in `startDir` already have the Astryx prompt in reach?
111
+ *
112
+ * Coding agents read the agent docs between the repository root and the folder
113
+ * they work in. So inside a git repository, init's block in any folder from
114
+ * `startDir` up to the repository root counts: a workspace package of a
115
+ * monorepo initialized at its root is set up. Outside a repository only
116
+ * `startDir` itself is checked, as {@link isAstryxInitialized} does. Both the
117
+ * per-command nudge and the postinstall nudges ask this.
118
+ *
119
+ * @param {string} [startDir=process.cwd()]
120
+ * @returns {boolean}
121
+ */
122
+ export function isAstryxPromptInReach(startDir = process.cwd()) {
123
+ const start = path.resolve(startDir);
124
+ const repositoryRoot = findRepositoryRoot(start);
125
+ if (!repositoryRoot) return isAstryxInitialized(start);
126
+ for (let dir = start; ; dir = path.dirname(dir)) {
127
+ if (isAstryxInitialized(dir)) return true;
128
+ if (dir === repositoryRoot) return false;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * The nearest folder at or above `start` that holds `.git` (a folder, or the
134
+ * file a worktree uses), or null outside a repository.
135
+ * @param {string} start
136
+ * @returns {string | null}
137
+ */
138
+ function findRepositoryRoot(start) {
139
+ for (let dir = start; ; ) {
140
+ if (fs.existsSync(path.join(dir, '.git'))) return dir;
141
+ const parent = path.dirname(dir);
142
+ if (parent === dir) return null;
143
+ dir = parent;
144
+ }
145
+ }
146
+
109
147
  /**
110
148
  * The one-line next-step both postinstall nudges print. Kept here so layer 1
111
149
  * (core) and layer 2 (cli) can never word it differently.
@@ -45,7 +45,7 @@ async function main() {
45
45
  contract.shouldNudge({
46
46
  scriptPath: HERE,
47
47
  npmCommand: process.env.npm_command,
48
- isSetUp: contract.isAstryxInitialized(root),
48
+ isSetUp: contract.isAstryxPromptInReach(root),
49
49
  })
50
50
  ) {
51
51
  process.stdout.write(contract.SETUP_NUDGE);
@@ -11,12 +11,15 @@
11
11
  */
12
12
 
13
13
  import {describe, it, expect, beforeEach, afterEach} from 'vitest';
14
+ import {spawnSync} from 'node:child_process';
14
15
  import * as fs from 'node:fs';
15
16
  import * as os from 'node:os';
16
17
  import * as path from 'node:path';
18
+ import {fileURLToPath} from 'node:url';
17
19
  // Core's own package-local contract copy — what actually runs at install time.
18
20
  import {
19
21
  isAstryxInitialized,
22
+ isAstryxPromptInReach,
20
23
  shouldNudge,
21
24
  AGENT_DOC_PATHS,
22
25
  INIT_MARKERS,
@@ -98,6 +101,49 @@ describe('core postinstall — one authoritative source, no drift', () => {
98
101
  write('AGENTS.md', `# doc\n${MARKER}\nbody`);
99
102
  expect(isAstryxInitialized(tmp)).toBe(cliLeaf.isAstryxInitialized(tmp));
100
103
  });
104
+
105
+ it('agrees with the CLI leaf on a monorepo package', () => {
106
+ fs.mkdirSync(path.join(tmp, '.git'));
107
+ write('AGENTS.md', MARKER);
108
+ write('packages/app/package.json', '{"name":"app"}');
109
+ const app = path.join(tmp, 'packages/app');
110
+ expect(isAstryxPromptInReach(app)).toBe(true);
111
+ expect(isAstryxPromptInReach(app)).toBe(cliLeaf.isAstryxPromptInReach(app));
112
+ });
113
+ });
114
+
115
+ // Install time, end to end: core's real script and its contract copy, installed
116
+ // under a monorepo's node_modules, run the way npm runs them for a package.
117
+ describe('core postinstall — inside a monorepo package', () => {
118
+ const CORE = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
119
+ const install = () => {
120
+ const installed = path.join(tmp, 'node_modules/@astryxdesign/core/scripts');
121
+ fs.mkdirSync(installed, {recursive: true});
122
+ for (const file of ['postinstall.mjs', 'agent-doc-state.mjs']) {
123
+ fs.copyFileSync(path.join(CORE, 'scripts', file), path.join(installed, file));
124
+ }
125
+ return spawnSync(process.execPath, [path.join(installed, 'postinstall.mjs')], {
126
+ env: {...process.env, INIT_CWD: path.join(tmp, 'packages/app'), npm_command: 'install'},
127
+ encoding: 'utf8',
128
+ });
129
+ };
130
+ beforeEach(() => {
131
+ fs.mkdirSync(path.join(tmp, '.git'));
132
+ write('packages/app/package.json', '{"name":"app"}');
133
+ });
134
+
135
+ it('stays quiet when init ran at the repository root', () => {
136
+ write('AGENTS.md', MARKER);
137
+ const r = install();
138
+ expect(r.status).toBe(0);
139
+ expect(r.stdout).toBe('');
140
+ });
141
+
142
+ it('still nudges when no folder up to the repository root ran init', () => {
143
+ const r = install();
144
+ expect(r.status).toBe(0);
145
+ expect(r.stdout).toMatch(/finish setup and install the Astryx agent prompt/);
146
+ });
101
147
  });
102
148
 
103
149
  describe('core postinstall — shouldNudge', () => {