@davidwells/extract-deps 0.1.14 → 0.1.15

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": "@davidwells/extract-deps",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Get imports and requires from a codebase without AST",
5
5
  "main": "src/index.js",
6
6
  "exports": {
package/src/dep-graph.js CHANGED
@@ -164,6 +164,10 @@ function resolveImportPathUncached(baseDir, importPath) {
164
164
  * @param {boolean} throwOnFirstError - Whether to throw immediately on first error
165
165
  */
166
166
  function verifyImportedValuesExist(dep, targetDeps, targetPath, sourcePath, errors, throwOnFirstError) {
167
+ // JSON modules have no export statements to check: the parsed value IS the default export
168
+ // (ESM, `with { type: 'json' }`) or module.exports (CJS require). Scanning the JSON text
169
+ // for exports found none and reported a missing default export for `require('./x.json')`.
170
+ if (/\.json$/i.test(targetPath)) return
167
171
  // Check if this is a destructured import
168
172
  if (dep.definitions && dep.definitions.expectsDestructuring && dep.definitions.destructuredParameters) {
169
173
  const importedNames = dep.definitions.destructuredParameters.map(p => p.name.replace(/,$/, ''))
package/src/index.js CHANGED
@@ -45,7 +45,9 @@ function extractDeps(code, filePath = '') {
45
45
  }
46
46
 
47
47
  function parseDeps(code) {
48
- const imports = getImports(code)
48
+ // Import-attribute imports (`import data from './x.json' with { type: 'json' }`) are
49
+ // dependencies too: without them a changed JSON file never reaches its importers
50
+ const imports = getImports(code, { includeAssertionImports: true })
49
51
  //console.log('imports', imports)
50
52
  const requires = getRequires(code)
51
53
  // console.log('requires', requires)
@@ -0,0 +1,79 @@
1
+ // JSON modules in the dependency graph (smart-ci sci-nrnc.4 / sci-nrnc.11)
2
+ const { test } = require('uvu')
3
+ const assert = require('uvu/assert')
4
+ const fs = require('fs')
5
+ const os = require('os')
6
+ const path = require('path')
7
+ const { resolveDepPaths, clearDepGraphCache } = require('../src/dep-graph')
8
+
9
+ function makeFiles(files) {
10
+ const dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'extract-deps-json-')))
11
+ for (const [rel, content] of Object.entries(files)) {
12
+ fs.mkdirSync(path.dirname(path.join(dir, rel)), { recursive: true })
13
+ fs.writeFileSync(path.join(dir, rel), content)
14
+ }
15
+ return dir
16
+ }
17
+
18
+ const VERIFY = { verifyGraph: true, verifyNodeModules: true, includeNodeModules: true, returnErrors: true }
19
+
20
+ test('CommonJS require of JSON: listed, and no "default export" error', () => {
21
+ clearDepGraphCache()
22
+ const dir = makeFiles({
23
+ 'handler.js': "const messages = require('./messages.json')\nmodule.exports.handler = async () => messages.hello\n",
24
+ 'messages.json': '{ "hello": "Foobar!!!" }\n',
25
+ })
26
+ try {
27
+ const { files, errors } = resolveDepPaths(path.join(dir, 'handler.js'), VERIFY)
28
+ assert.ok(files.includes(path.join(dir, 'messages.json')), 'messages.json is a dependency')
29
+ assert.equal((errors || []).map(e => e.message), [])
30
+ } finally {
31
+ fs.rmSync(dir, { recursive: true, force: true })
32
+ }
33
+ })
34
+
35
+ test('CommonJS destructuring require of JSON: no export errors', () => {
36
+ clearDepGraphCache()
37
+ const dir = makeFiles({
38
+ 'handler.js': "const { hello } = require('./messages.json')\nmodule.exports.handler = async () => hello\n",
39
+ 'messages.json': '{ "hello": "hi" }\n',
40
+ })
41
+ try {
42
+ const { errors } = resolveDepPaths(path.join(dir, 'handler.js'), VERIFY)
43
+ assert.equal((errors || []).map(e => e.message), [])
44
+ } finally {
45
+ fs.rmSync(dir, { recursive: true, force: true })
46
+ }
47
+ })
48
+
49
+ test('ESM import of JSON with import attributes (with / assert): listed as a dependency', () => {
50
+ clearDepGraphCache()
51
+ const dir = makeFiles({
52
+ 'src/app.mjs': "import contract from '../docs/api.json' with { type: 'json' }\nimport legacy from './legacy.json' assert { type: 'json' }\nexport const h = () => [contract, legacy]\n",
53
+ 'src/legacy.json': '{}\n',
54
+ 'docs/api.json': '{ "version": 1 }\n',
55
+ })
56
+ try {
57
+ const { files, errors } = resolveDepPaths(path.join(dir, 'src/app.mjs'), VERIFY)
58
+ assert.ok(files.includes(path.join(dir, 'docs/api.json')), 'with { type: json } import is a dependency')
59
+ assert.ok(files.includes(path.join(dir, 'src/legacy.json')), 'assert { type: json } import is a dependency')
60
+ assert.equal((errors || []).map(e => e.message), [])
61
+ } finally {
62
+ fs.rmSync(dir, { recursive: true, force: true })
63
+ }
64
+ })
65
+
66
+ test('a missing JSON import is still reported', () => {
67
+ clearDepGraphCache()
68
+ const dir = makeFiles({
69
+ 'app.mjs': "import contract from './missing.json' with { type: 'json' }\nexport const h = () => contract\n",
70
+ })
71
+ try {
72
+ const { errors } = resolveDepPaths(path.join(dir, 'app.mjs'), VERIFY)
73
+ assert.ok((errors || []).length > 0, 'missing.json is an error')
74
+ } finally {
75
+ fs.rmSync(dir, { recursive: true, force: true })
76
+ }
77
+ })
78
+
79
+ test.run()
@@ -22,7 +22,8 @@ export function depGraph(filePath: string, options?: {
22
22
  errors: Error[];
23
23
  } | undefined;
24
24
  /**
25
- * Get all dependency file paths in build order (dependencies first), deduplicated
25
+ * Get all dependency file paths in build order (dependencies first), deduplicated.
26
+ * Files in an import cycle are kept together, in discovery order.
26
27
  * @param {string} filePath - Entry file path
27
28
  * @param {object} options - Options to pass to depGraph
28
29
  * @param {boolean} [options.verifyGraph] - If true, verify imports/exports. Default: true
@@ -42,4 +43,6 @@ export function resolveDepPaths(filePath: string, options?: {
42
43
  builtIns: string[];
43
44
  errors?: Error[];
44
45
  };
46
+ /** Drop memoized parses and resolutions (e.g. between runs in a long-lived process). */
47
+ export function clearDepGraphCache(): void;
45
48
  //# sourceMappingURL=dep-graph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dep-graph.d.ts","sourceRoot":"","sources":["../src/dep-graph.js"],"names":[],"mappings":"AAmUA;;;;;;;;;;;GAWG;AACH,mCAVW,MAAM,YAEd;IAA0B,UAAU,GAA5B,OAAO;IACW,WAAW,GAA7B,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,kBAAkB,GAApC,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,YAAY,GAA9B,OAAO;CACf,GAAU,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAC;IAAC,KAAK,gBAAM;IAAC,MAAM,EAAE,KAAK,EAAE,CAAA;CAAC,GAAC,SAAS,CA8BzE;AAGD;;;;;;;;;GASG;AACH,0CARW,MAAM,YAEd;IAA0B,WAAW,GAA7B,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,YAAY,GAA9B,OAAO;CACf,GAAU;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;CAAE,CAkF5F"}
1
+ {"version":3,"file":"dep-graph.d.ts","sourceRoot":"","sources":["../src/dep-graph.js"],"names":[],"mappings":"AA6ZA;;;;;;;;;;;GAWG;AACH,mCAVW,MAAM,YAEd;IAA0B,UAAU,GAA5B,OAAO;IACW,WAAW,GAA7B,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,kBAAkB,GAApC,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,YAAY,GAA9B,OAAO;CACf,GAAU,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAC;IAAC,KAAK,gBAAM;IAAC,MAAM,EAAE,KAAK,EAAE,CAAA;CAAC,GAAC,SAAS,CA8BzE;AAgJD;;;;;;;;;;GAUG;AACH,0CARW,MAAM,YAEd;IAA0B,WAAW,GAA7B,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,YAAY,GAA9B,OAAO;CACf,GAAU;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;CAAE,CAgD5F;AArkBD,wFAAwF;AACxF,2CAKC"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-cjs-exports.d.ts","sourceRoot":"","sources":["../src/get-cjs-exports.js"],"names":[],"mappings":"AAoCA,gDAgDC;AA+BD;;;;;;;;IAqGC;AAED;;;;;;;IAqBC;AAED;;;;;;;IAqBC;AAED;;;;;;;IAiDC;AAED;;;;;;;;;IAwEC"}
1
+ {"version":3,"file":"get-cjs-exports.d.ts","sourceRoot":"","sources":["../src/get-cjs-exports.js"],"names":[],"mappings":"AAyCA,gDAoDC;AA+BD;;;;;;;;IAqGC;AAED;;;;;;;IAqBC;AAED;;;;;;;IAqBC;AAED;;;;;;;IAiDC;AAED;;;;;;;;;IAwEC"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-exports.d.ts","sourceRoot":"","sources":["../src/get-exports.js"],"names":[],"mappings":"AAiBA,+DAqCC;AAED;;;;;;;;IAqDC;AAED;;;;;;IAeC;AAED,uEAuBC;AAED;;;;;;IAeC;AAED;;;;;;;IAgBC;AAED;;;;;;IAeC;AAED;;;;;;;;;;;;IA0BC"}
1
+ {"version":3,"file":"get-exports.d.ts","sourceRoot":"","sources":["../src/get-exports.js"],"names":[],"mappings":"AAiBA,+DAsCC;AAED;;;;;;;;IAqDC;AAED;;;;;;IAeC;AAED,uEAuBC;AAED;;;;;;IAeC;AAED;;;;;;;IAgBC;AAED;;;;;;IAeC;AAED;;;;;;;;;;;;IA0BC"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-imports.d.ts","sourceRoot":"","sources":["../src/get-imports.js"],"names":[],"mappings":"AAsCA;;;;;;;;;;IAcC;AAGD;;;;;;;;;;IAsCC;AAED;;;;;;;;;IAkBC;AAED;;;;;;;;;;;IAuBC;AAED;;;;;;;;;;;;;IA0BC"}
1
+ {"version":3,"file":"get-imports.d.ts","sourceRoot":"","sources":["../src/get-imports.js"],"names":[],"mappings":"AAsCA;;;;;;;;;;IAiBC;AAGD;;;;;;;;;;IAsCC;AAED;;;;;;;;;IAkBC;AAED;;;;;;;;;;;IAuBC;AAED;;;;;;;;;;;;;IA0BC"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-requires.d.ts","sourceRoot":"","sources":["../src/get-requires.js"],"names":[],"mappings":"AA0GA;;;;;;;;;;;IAUC;AArCD;;;;;;;;;;;IAyBC;AA/FD;;;;;;;;;;;IA6BC"}
1
+ {"version":3,"file":"get-requires.d.ts","sourceRoot":"","sources":["../src/get-requires.js"],"names":[],"mappings":"AA0GA;;;;;;;;;;;IAWC;AAtCD;;;;;;;;;;;IAyBC;AA/FD;;;;;;;;;;;IA6BC"}
package/types/utils.d.ts CHANGED
@@ -63,6 +63,14 @@ export function stripStringContents(code: string): string;
63
63
  * @returns {string}
64
64
  */
65
65
  export function stripCommentsAndStrings(code: string): string;
66
+ /**
67
+ * `stripStringContents(stripComments(code))`, the prelude of the import, require
68
+ * and export scanners (comments first, then strings: see getImports). Pure in
69
+ * `code`; the latest result is kept for the next scanner of the same text.
70
+ * @param {string} code
71
+ * @returns {string}
72
+ */
73
+ export function stripCommentsThenStrings(code: string): string;
66
74
  /**
67
75
  * Check if a module specifier is a Node.js built-in
68
76
  * @param {string} moduleName - The module specifier (e.g., 'fs', 'node:fs/promises')
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAsQA;;;;;;GAMG;AACH,oCAHW,MAAM,GACJ,MAAM,CAIlB;AAtQD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,0CAHW,MAAM,GACJ,MAAM,CA6ClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,8CAHW,MAAM,GACJ,MAAM,CAIlB;AAkKD;;;;GAIG;AACH,sCAHW,MAAM,GACJ,OAAO,CAUnB;AAED,+DAEC;AAED,yEAKC;AAgFD,6DA+DC;AAGD,yDAqCC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AA0RA;;;;;;GAMG;AACH,oCAHW,MAAM,GACJ,MAAM,CAIlB;AA1RD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,0CAHW,MAAM,GACJ,MAAM,CAuDlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,8CAHW,MAAM,GACJ,MAAM,CAIlB;AAiLD;;;;;;GAMG;AACH,+CAHW,MAAM,GACJ,MAAM,CAQlB;AAED;;;;GAIG;AACH,sCAHW,MAAM,GACJ,OAAO,CAUnB;AAED,+DAEC;AAED,yEAKC;AAgFD,6DA+DC;AAGD,yDAqCC"}